
'=====================================================================
'模 块 名：ModbusServer

'描    述：Modbus-ASCII/RTU/TCP协议Server类，启动单独线程轮询各个从机

'依赖模块：DataLink类, ModbusBase模块

'调用方法：《ModbusServer类库使用说明.txt》文件

'原始版本：1.00
'作    者：刘德亮
'完成日期：

'修改版本：1.01
'修改作者：高捷朝
'修改日期：2019-11-25

'修改版本：1.02
'修改作者：刘德亮
'修改日期：2020-4-10
'修改记录：1.JSON文件中的"modbus id" "1"去掉，巡检使用各个寄存器的单独定义
'            Reg.RegBlockIN(i).ID,  Reg.RegBlockOUT(i).ID
'          2.RTU_WaitAckFrame()添加识别长度不够的代码
'          3.规范数据类型Long=>Int32,Integer=>Int16
'          4.其它带Word和DWord的函数都改名为Uint6和Uint32
'          5.添加IN_ReadInt16(),IN_ReadInt32(),OUT_ReadInt16(),OUT_ReadInt32()
'          6.添加IN_ReadString(),OUT_ReadString(),OUT_WriteString()
'          7.ModbusIsOK()改IsModbusOK()
'          8.OUT_IsGood()改OUT_TimeOut(),IN_IsGood()改IN_TimeOut()
'          9.添加ClearInputBuffer()发送命令前 清空输入缓冲区
'          10.AnalysisJsonFile()加入找不到指定字符串的默认值
'          11.MsgBox()不执行，只返回错误号
'          12.JSON配置文件中增加"bit mode"
'             ModbusRegBlockIN和ModbusRegBlockOUT添加BitMode， 支持位模式读写
'修改日期：2020-6-15
'          13.JSON文件中增加"xwrite mode"
'             ModbusRegBlockOUT添加XWriteMode， 支持多寄存器写模式
'          14.添加命令宏定义CMD_*
'修改作者：刘德亮
'修改日期：2020-7-16
'修改记录：15.ASCII_WaitAckFrame(),RTU_WaitAckFrame(),TCP_WaitAckFrame()中定时用sleep()不准确，改用如下CPU时间片
'             Dim intTimeTick_pre As Integer = My.Computer.Clock.TickCount
'修改作者：刘德亮
'修改日期：2020-7-24
'修改记录：16.添加OUT_ReadBuffer****()等函数，读输出寄存器的保持状态，OUT_ReadUint16()是巡检读回来的从机当前值输出值
'          17.整理出CheckJsonNumberValue()用于AnalysisJsonFile()检查JSON数值项，且检查条件加严
'修改日期：2020-7-29
'          18.JSON配置文件加入"write check"
'          19.ModbusRegBlockIN和ModbusRegBlockOUT添加Enable，可以实时禁用以便提高效率
'          20.增加IN_Enable(),IN_Disable(),IN_IsEnable()和OUT_Enable(),OUT_Disable(),OUT_IsEnable()实时改变Enable
'修改日期：2020-7-31
'          21.MsgBox()完善提示信息
'          22.使用方法单独在《ModbusServer类库使用说明.txt》文件中

'修改版本：1.03
'修改作者：刘德亮
'修改日期：2021-6-14
'          1.增加OUT_IsAllWrited()以便“步进电机控制器”有时序要求时使用
'          2.增加IN_IsBlockAvailable()和OUT_IsBlockAvailable()以便简化各处检查代码量

'保留：ASCII和TCP未实现BitMode和XWriteMode，RTU未实现15命令
'=====================================================================

Imports System.Threading

Namespace FL_8x0.Common


    Public Class ModbusServer

        '结构体-输入寄存器段
        Private Structure ModbusRegBlockIN
            Dim Enable As Boolean     '是否有效（False=禁用，True=使能（默认））
            Dim ID As Byte            'ID=1-254
            Dim BitMode As Boolean    '位模式读（False=字寄存器（默认），True=位寄存器）
            Dim StartAddr As UInt16   '起始地址=0000-FFFF
            Dim EndAddr As UInt16     '结束地址=0000-FFFF
            Dim Item() As UInt16      '寄存器当前值（数组）
            Dim TimerOutCount As UInt16    '超时次数
        End Structure

        '结构体-输出寄存器段
        Private Structure ModbusRegBlockOUT
            Dim Enable As Boolean     '是否有效（False=禁用，True=使能（默认））
            Dim ID As Byte            'ID=1-254
            Dim BitMode As Boolean    '位模式读写（False=字寄存器（默认），True=位寄存器）
            Dim XWriteMode As Boolean '写模式（False=单寄存器写6/5（默认），True=多寄存器写16/15）
            Dim StartAddr As UInt16   '起始地址=0000-FFFF
            Dim EndAddr As UInt16     '结束地址=0000-FFFF
            Dim Item() As UInt16           '寄存器当前值（数组），由从机读回的
            Dim ItemWrite() As UInt16      '寄存器写入值（数组）
            Dim ItemNeedWrite() As Boolean '需要写入数据（避免设备端主动变化后强行写设备）
            Dim WriteCheckEnable As Boolean  '写检查模式（False=不检查，True=写入时检查是否需要写输出（默认））
            Dim TimerOutCount As UInt16    '超时次数
        End Structure

        '结构体-modbus
        Private Structure ModbusRegister
            Dim ResponseTime As UInt32   '命令响应时间（超时时间）(ms)
            Dim IntervalTime As UInt32   '2个命令间的时间间隔(ms)

            Dim Protocol As String       'Modbus子协议（ASCII, RTU, TCP）
            Dim RegBlockIN() As ModbusRegBlockIN     '输入寄存器块（数组）
            Dim RegBlockOUT() As ModbusRegBlockOUT   '输出寄存器块（数组）
        End Structure


        '错误号
        Public Const MODBUS_OK As UInt16 = &H0
        Public Const MODBUS_ERR1 As UInt16 = &H10 '参数错误
        Public Const MODBUS_ERR2 As UInt16 = &H11 '配置文件打开错误
        Public Const MODBUS_ERR3 As UInt16 = &H12 '配置文件格式错误
        Public Const MODBUS_ERR4 As UInt16 = &H13 '通讯接口打开错误
        Public Const MODBUS_ERR5 As UInt16 = &H14 '通讯接口关闭错误
        Public Const MODBUS_ERR6 As UInt16 = &H15 '网络未连接
        Public Const MODBUS_ERR7 As UInt16 = &H16 '服务未启动
        Public Const MODBUS_ERR8 As UInt16 = &H17 '寄存器地址不存在
        Public Const MODBUS_ERR As UInt16 = &HFFFF


        '类成员变量
        Private strLinkConfig As String     '通讯接口连接配置
        Private PortLink As DataLink        '通讯接口句柄

        Private Reg As ModbusRegister       'Modbus寄存器结构
        Private threadLock As Object

        Private tdThread As Thread          '通讯处理线程句柄
        Private blnRun As Boolean

        Private blnWorking As Boolean       'server是否正在运行

        Private strRxDBuffer As String      '接收数据缓冲区
        Private strTxDBuffer As String      '发送数据缓冲区

        Private bytRxDBuffer As Byte()      '接收数据缓冲区
        Private bytTxDBuffer As Byte()      '发送数据缓冲区

        Private uiFrameID As UInt16         '帧序号（自动＋1），TCP模式使用
        Private blnModbusOK As Boolean       'Modbus通讯正常

        '寄存器操作命令
        Private Const CMD_READ_IN_REG = 4       '读输入寄存器
        Private Const CMD_READ_OUT_REG = 3      '读输出寄存器
        Private Const CMD_WRITE_OUT_REG = 6     '写单输出寄存器
        Private Const CMD_WRITE_OUT_REGS = 16   '写多输出寄存器

        '位操作命令
        Private Const CMD_READ_IN_BIT = 2       '读输入位(开关)
        Private Const CMD_READ_OUT_BIT = 1      '读输出位(线圈)
        Private Const CMD_WRITE_OUT_BIT = 5     '写单输出位(线圈)
        Private Const CMD_WRITE_OUT_BITS = 15   '写多输出位(线圈)




        '=====================================================================
        '函 数 名:New
        '参    数:
        '返 回 值:
        '功能描述:构造函数
        '=====================================================================
        Public Sub New()
            '初始化
            strLinkConfig = ""
            PortLink = New DataLink

            Reg = New ModbusRegister
            threadLock = New Object()

            tdThread = Nothing  '启动线程时再申请空间
            blnWorking = False

            strRxDBuffer = ""
            strTxDBuffer = ""

            bytRxDBuffer = Nothing
            bytTxDBuffer = Nothing

            blnModbusOK = False
            uiFrameID = 0
        End Sub



        '=====================================================================
        '函 数 名:Finalize
        '参    数:
        '返 回 值:
        '功能描述:析构函数
        '=====================================================================
        Protected Overrides Sub Finalize()
            '关闭线程
            If Not IsNothing(tdThread) Then
                tdThread.Abort()  '终止线程

                If tdThread.ThreadState <> ThreadState.Unstarted Then
                    tdThread.Join()  '等待完全终止
                End If

                tdThread = Nothing
            End If

            '关闭通讯接口
            If Not IsNothing(PortLink) Then
                If PortLink.IsLinkOpening() Then
                    PortLink.CloseLink()
                End If

                PortLink = Nothing
            End If

            '清空
            strLinkConfig = Nothing
            Reg = Nothing
            threadLock = Nothing

            blnWorking = False

            strRxDBuffer = Nothing
            strTxDBuffer = Nothing

            bytRxDBuffer = Nothing
            bytTxDBuffer = Nothing

            blnModbusOK = False

            MyBase.Finalize()
        End Sub



        '=====================================================================
        '函 数 名:IsServerRunning
        '参    数:
        '返 回 值:True/False
        '功能描述:server是否正在运行
        '=====================================================================
        Public Function IsServerRunning() As Boolean
            Return blnWorking
        End Function



        '=====================================================================
        '函 数 名:IsModbusOK
        '参    数:
        '返 回 值:True/False
        '功能描述:返回Modbus运行状态
        '=====================================================================
        Public Function IsModbusOK() As Boolean
            Return blnModbusOK
        End Function



        '=====================================================================
        '函 数 名:ClearInputBuffer
        '参    数:
        '返 回 值:
        '功能描述:清空输入缓冲区
        '=====================================================================
        Public Sub ClearInputBuffer()
            While PortLink.Available > 0
                Dim bytBuffer(0 To 1024) As Byte
                PortLink.ReadData(bytBuffer, bytBuffer.Count)
            End While
        End Sub



        '=====================================================================
        '函 数 名: CheckJsonNumberValue
        '参    数: jt = JSON句柄
        '          strName = 名称
        '          sngMax = 最大值
        '          sngMin = 最小值
        '返 回 值: True = OK
        '          False = 输入错误
        '功能描述: 检查JSON格式的数值有效性（数字，最大值，最小值）
        '=====================================================================
        Private Function CheckJsonNumberValue(ByRef jt As Newtonsoft.Json.Linq.JToken, ByVal strName As String,
                                              ByVal sngMax As Single, ByVal sngMin As Single) As UInt16

            '参数是否存在
            If IsNothing(jt) Then
                Return MODBUS_ERR3
            End If

            If IsNothing(jt.Item(strName)) Then
                Return MODBUS_ERR3
            End If

            '是否数值型
            If Not IsNumeric(jt.Item(strName).ToString()) Then
                Return MODBUS_ERR3
            End If

            '是否超出范围
            If sngMin <> 0 And sngMax <> 0 Then '范围都不等于0时
                If CSng(jt.Item(strName).ToString()) < sngMin Or CSng(jt.Item(strName).ToString()) > sngMax Then
                    Return MODBUS_ERR3
                End If
            End If

            Return MODBUS_OK
        End Function



        '=====================================================================
        '函 数 名:AnalysisJsonFile
        '参    数:strJsonFile = JSON文件完整路径
        '         strConfig = （引用）数据接口的配置字符串
        '         Reg = （引用）Modbus寄存器结构
        '返 回 值:MODBUS_OK, 
        '         MODBUS_ERR*
        '功能描述:解析JSON配置文件
        '=====================================================================
        Private Function AnalysisJsonFile(ByVal strJsonFile As String, ByRef strConfig As String) As UInt16
            '检查参数
            If strJsonFile = "" Then
                Return MODBUS_ERR1
            End If

            '检查文件有效性
            If Not System.IO.File.Exists(strJsonFile) Then
                Return MODBUS_ERR2
            End If

            '读出JSON字符串
            Dim strJsonData As String

            Try
                strJsonData = My.Computer.FileSystem.ReadAllText(strJsonFile, System.Text.Encoding.UTF8)

            Catch ex As Exception
                MsgBox(ex.Message, vbOKOnly, ex.Source)
                Return MODBUS_ERR2
            End Try

            '解析配置文件
            Dim jsonResult As Newtonsoft.Json.Linq.JObject 'JSON句柄

            Try
                jsonResult = Newtonsoft.Json.Linq.JObject.Parse(strJsonData)    '执行解析

            Catch ex As Exception
                MsgBox(ex.Message, vbOKOnly, ex.Source)
                Return MODBUS_ERR2
            End Try

            Try
                '产生通讯接口配置字符串
                If IsNothing(jsonResult.Item("type")) Then
                    Return MODBUS_ERR3
                End If

                Dim config As String

                Dim strLinkType As String = jsonResult.Item("type").ToString()  '获取连接类型
                config = strLinkType

                If strLinkType = "uart" Then
                    If IsNothing(jsonResult.Item("uart port")) Then
                        Return MODBUS_ERR3
                    End If
                    config &= ", " & jsonResult.Item("uart port").ToString() '串口号

                    If CheckJsonNumberValue(jsonResult, "uart baud(bps)", 115200, 300) <> MODBUS_OK Then
                        Return MODBUS_ERR3
                    End If
                    config &= ", " & jsonResult.Item("uart baud(bps)").ToString() '波特率

                    If CheckJsonNumberValue(jsonResult, "uart data", 8, 7) <> MODBUS_OK Then
                        Return MODBUS_ERR3
                    End If
                    config &= ", " & jsonResult.Item("uart data").ToString() '数据位

                    If IsNothing(jsonResult.Item("uart Parity")) Then
                        Return MODBUS_ERR3
                    End If
                    config &= ", " & jsonResult.Item("uart Parity").ToString() '校验位

                    If CheckJsonNumberValue(jsonResult, "uart stop", 2, 1) <> MODBUS_OK Then
                        Return MODBUS_ERR3
                    End If
                    config &= ", " & jsonResult.Item("uart stop").ToString() '停止位

                ElseIf strLinkType = "tcp-server" Then
                    If CheckJsonNumberValue(jsonResult, "local port", 65535, 0) <> MODBUS_OK Then
                        Return MODBUS_ERR3
                    End If
                    config &= ", " & jsonResult.Item("local port").ToString() '本地端口

                ElseIf strLinkType = "tcp-client" Then
                    If IsNothing(jsonResult.Item("remote ip")) Then
                        Return MODBUS_ERR3
                    End If
                    config &= ", " & jsonResult.Item("remote ip").ToString() '服务器IP

                    If CheckJsonNumberValue(jsonResult, "remote port", 65535, 0) <> MODBUS_OK Then
                        Return MODBUS_ERR3
                    End If
                    config &= ", " & jsonResult.Item("remote port").ToString() '服务器端口

                ElseIf strLinkType = "udp" Then
                    If CheckJsonNumberValue(jsonResult, "local port", 65535, 0) <> MODBUS_OK Then
                        Return MODBUS_ERR3
                    End If
                    config &= ", " & jsonResult.Item("local port").ToString() '本地端口

                    If IsNothing(jsonResult.Item("remote ip")) Then
                        Return MODBUS_ERR3
                    End If
                    config &= ", " & jsonResult.Item("remote ip").ToString() '服务器IP

                    If CheckJsonNumberValue(jsonResult, "remote port", 65535, 0) <> MODBUS_OK Then
                        Return MODBUS_ERR3
                    End If
                    config &= ", " & jsonResult.Item("remote port").ToString() '服务器端口

                Else
                    MsgBox("strLinkType=" & strLinkType) '未知连接类型
                    Return MODBUS_ERR3
                End If

                strConfig = config

                '时间
                If CheckJsonNumberValue(jsonResult, "response time(ms)", 99999, 1) <> MODBUS_OK Then
                    Return MODBUS_ERR3
                End If
                Reg.ResponseTime = jsonResult.Item("response time(ms)").ToString()  '命令响应时间

                If CheckJsonNumberValue(jsonResult, "interval time(ms)", 99999, 1) <> MODBUS_OK Then
                    Return MODBUS_ERR3
                End If
                Reg.IntervalTime = jsonResult.Item("interval time(ms)").ToString()  '两个命令间的间隔

                '子协议
                If IsNothing(jsonResult.Item("protocol")) Then
                    Return MODBUS_ERR3
                End If
                Reg.Protocol = jsonResult.Item("protocol").ToString() 'modbus协议子类

                If Reg.Protocol <> "ASCII" And Reg.Protocol <> "RTU" And Reg.Protocol <> "TCP" Then
                    Return MODBUS_ERR3 '未知协议子类
                End If

                Dim jt As Newtonsoft.Json.Linq.JToken 'JSON句柄
                Dim index As UInt16         '寄存器段区索引
                Dim Addr_start As UInt16    '段区开始地址
                Dim Addr_end As UInt16      '段区结束地址

                '产生输入寄存器
                If IsNothing(jsonResult.Item("input")) Then '没有输入寄存器
                    Reg.RegBlockIN = Nothing
                End If

                If jsonResult.Item("input").Count > 0 Then
                    ReDim Reg.RegBlockIN(0 To (jsonResult.Item("input").Count - 1))    '重新分配空间

                    index = 0
                    For Each jt In jsonResult.Item("input")
                        '获取Enable
                        If IsNothing(jt.Item("Enable")) Then
                            Reg.RegBlockIN(index).Enable = True      'True=使能（默认）
                        Else
                            If CheckJsonNumberValue(jt, "Enable", 1, 0) <> MODBUS_OK Then
                                Return MODBUS_ERR3
                            End If
                            Reg.RegBlockIN(index).Enable = CBool(jt.Item("Enable").ToString())      '获取Enable
                        End If

                        '获取ID
                        If CheckJsonNumberValue(jt, "ID", 255, 0) <> MODBUS_OK Then
                            Return MODBUS_ERR3
                        End If
                        Reg.RegBlockIN(index).ID = CByte(jt.Item("ID").ToString())      '获取modbus id号

                        '获取bit mode
                        If IsNothing(jt.Item("bit mode")) Then
                            Reg.RegBlockIN(index).BitMode = False      'False=字寄存器（默认）
                        Else
                            If CheckJsonNumberValue(jt, "bit mode", 1, 0) <> MODBUS_OK Then
                                Return MODBUS_ERR3
                            End If
                            Reg.RegBlockIN(index).BitMode = CBool(jt.Item("bit mode").ToString())      '获取BitMode
                        End If

                        '获取开始地址
                        If CheckJsonNumberValue(jt, "start addr", 65535, 0) <> MODBUS_OK Then
                            Return MODBUS_ERR3
                        End If
                        Addr_start = CUInt(jt.Item("start addr").ToString())

                        '获取结束地址
                        If CheckJsonNumberValue(jt, "end addr", 65535, 0) <> MODBUS_OK Then
                            Return MODBUS_ERR3
                        End If
                        Addr_end = CUInt(jt.Item("end addr").ToString())

                        '地址范围错误时  '交换为正确的
                        If Addr_end < Addr_start Then
                            Dim tmp As UInt16
                            tmp = Addr_end
                            Addr_end = Addr_start
                            Addr_start = tmp
                        End If

                        '每个块区寄存器个数只能1-127个
                        If Addr_end - Addr_start > 126 Then
                            Return MODBUS_ERR3
                        End If

                        Reg.RegBlockIN(index).StartAddr = Addr_start
                        Reg.RegBlockIN(index).EndAddr = Addr_end

                        ReDim Reg.RegBlockIN(index).Item(0 To (Addr_end - Addr_start))    '重新分配空间

                        '清空寄存器
                        Dim i As UInt16
                        For i = 0 To Reg.RegBlockIN(index).Item.Count - 1
                            Reg.RegBlockIN(index).Item(i) = 0
                        Next

                        index += 1
                    Next
                End If

                '产生输出寄存器
                If IsNothing(jsonResult.Item("output")) Then
                    Reg.RegBlockOUT = Nothing
                End If

                If jsonResult.Item("output").Count > 0 Then
                    ReDim Reg.RegBlockOUT(0 To (jsonResult.Item("output").Count - 1))    '重新分配空间

                    index = 0
                    For Each jt In jsonResult.Item("output")
                        '获取Enable
                        If IsNothing(jt.Item("Enable")) Then
                            Reg.RegBlockOUT(index).Enable = True      'True=使能（默认）
                        Else
                            If CheckJsonNumberValue(jt, "Enable", 1, 0) <> MODBUS_OK Then
                                Return MODBUS_ERR3
                            End If
                            Reg.RegBlockOUT(index).Enable = CBool(jt.Item("Enable").ToString())      '获取Enable
                        End If

                        '获取ID
                        If CheckJsonNumberValue(jt, "ID", 255, 1) <> MODBUS_OK Then
                            Return MODBUS_ERR3
                        End If
                        Reg.RegBlockOUT(index).ID = CByte(jt.Item("ID").ToString())

                        '获取bit mode
                        If IsNothing(jt.Item("bit mode")) Then
                            Reg.RegBlockOUT(index).BitMode = False      'False=字寄存器（默认）
                        Else
                            If CheckJsonNumberValue(jt, "bit mode", 1, 0) <> MODBUS_OK Then
                                Return MODBUS_ERR3
                            End If
                            Reg.RegBlockOUT(index).BitMode = CBool(jt.Item("bit mode").ToString())      '获取BitMode
                        End If

                        '获取write mode
                        If IsNothing(jt.Item("xwrite mode")) Then
                            Reg.RegBlockOUT(index).XWriteMode = False      'False=单寄存器写6/5（默认）
                        Else
                            If CheckJsonNumberValue(jt, "xwrite mode", 1, 0) <> MODBUS_OK Then
                                Return MODBUS_ERR3
                            End If
                            Reg.RegBlockOUT(index).XWriteMode = CBool(jt.Item("xwrite mode").ToString())      '获取WriteMode
                        End If

                        '获取write check
                        If IsNothing(jt.Item("write check")) Then
                            Reg.RegBlockOUT(index).WriteCheckEnable = True      'True=写入时检查是否需要写输出（默认）
                        Else
                            If CheckJsonNumberValue(jt, "write check", 1, 0) <> MODBUS_OK Then
                                Return MODBUS_ERR3
                            End If
                            Reg.RegBlockOUT(index).WriteCheckEnable = CBool(jt.Item("write check").ToString())      '获取WriteMode
                        End If

                        '获取开始地址   
                        If CheckJsonNumberValue(jt, "start addr", 65535, 0) <> MODBUS_OK Then
                            Return MODBUS_ERR3
                        End If
                        Addr_start = CUInt(jt.Item("start addr").ToString())

                        '获取结束地址
                        If CheckJsonNumberValue(jt, "end addr", 65535, 0) <> MODBUS_OK Then
                            Return MODBUS_ERR3
                        End If
                        Addr_end = CUInt(jt.Item("end addr").ToString())

                        '地址范围错误时  '交换为正确的
                        If Addr_end < Addr_start Then
                            Dim tmp As UInt16
                            tmp = Addr_end
                            Addr_end = Addr_start
                            Addr_start = tmp
                        End If

                        '每个块区寄存器个数只能1-127个
                        If Addr_end - Addr_start > 126 Then
                            Return MODBUS_ERR3
                        End If

                        Reg.RegBlockOUT(index).StartAddr = Addr_start
                        Reg.RegBlockOUT(index).EndAddr = Addr_end

                        ReDim Reg.RegBlockOUT(index).Item(0 To (Addr_end - Addr_start))          '重新分配空间
                        ReDim Reg.RegBlockOUT(index).ItemWrite(0 To (Addr_end - Addr_start))     '重新分配空间
                        ReDim Reg.RegBlockOUT(index).ItemNeedWrite(0 To (Addr_end - Addr_start)) '重新分配空间

                        '清空寄存器
                        Dim i As UInt16
                        For i = 0 To Reg.RegBlockOUT(index).Item.Count - 1
                            Reg.RegBlockOUT(index).Item(i) = 0
                            Reg.RegBlockOUT(index).ItemWrite(i) = 0
                            Reg.RegBlockOUT(index).ItemNeedWrite(i) = False
                        Next

                        index += 1 '块区索引+1
                    Next
                End If

            Catch ex As Exception
                MsgBox(ex.Message, vbOKOnly, ex.Source)
                Return MODBUS_ERR3
            End Try

            Return MODBUS_OK
        End Function



        '=====================================================================
        '函 数 名:StartServer
        '参    数:strJsonFile = JSON文件完整路径
        '返 回 值:MODBUS_OK, 
        '         MODBUS_ERR*
        '功能描述:启动Modbus后台服务
        '=====================================================================
        Public Function StartServer(ByVal strJsonFile As String) As UInt16

            uiFrameID = 0

            Dim err As UInt16
            err = AnalysisJsonFile(strJsonFile, strLinkConfig) '读取JSON配置文件

            If err = MODBUS_OK Then
                err = PortLink.OpenLink(strLinkConfig) '打开通讯接口

                If err = MODBUS_OK Then
                    Volatile.Write(blnRun, True)
                    tdThread = New Thread(AddressOf Thread_Timer)   '启动轮询线程
                    tdThread.Start()

                    blnWorking = True
                    blnModbusOK = False    '未收到数据
                End If
            End If

            Return err
        End Function



        '=====================================================================
        '函 数 名:StopServer
        '参    数:
        '返 回 值:MODBUS_OK, 
        '         MODBUS_ERR*
        '功能描述:停止Modbus后台服务
        '=====================================================================
        Public Function StopServer() As UInt16
            Try
                '关闭线程
                If Not IsNothing(tdThread) Then
                    tdThread.Abort()  '终止线程

                    If tdThread.ThreadState <> ThreadState.Unstarted Then
                        tdThread.Join()  '等待完全终止
                    End If

                    tdThread = Nothing  '启动线程时再申请空间
                End If

                '关闭通讯接口
                If PortLink.IsLinkOpening() Then
                    PortLink.CloseLink()
                End If
            Catch ex As Exception

            End Try
            blnWorking = False
            blnModbusOK = False
            Return MODBUS_OK
        End Function



        '=====================================================================
        '函 数 名: ASCII_MakeCmdFrame
        '参    数: ID = 从机ID号
        '          ucCmd = 命令
        '          uiAddr = 数据1
        '          uiData = 数据2
        '返 回 值: 帧字符串
        '功能描述: 生成命令帧，双字数据型
        '=====================================================================
        Private Function ASCII_MakeCmdFrame(ByVal ID As Byte, ByVal ucCmd As Byte, ByVal uiAddr As UInt16, ByVal uiData As UInt16) As String
            Dim str1 As String = ""
            'ID 命令码 地址 ,数据
            str1 = ByteToHexString(ID) & ByteToHexString(ucCmd) & Uint16ToHexString(uiAddr) & Uint16ToHexString(uiData)

            str1 = ":" & str1 & LRC(str1) & vbCrLf
            Return str1
        End Function



        '=====================================================================
        '函 数 名: ASCII_WaitAckFrame
        '参    数: strBuffer = 接收数据缓冲区
        '返 回 值: 0 = OK
        '          1 = 超时
        '          2 = 帧校验错误
        '          3 = 系统错误
        '          4 = 命令错误
        '功能描述: 等待节点的完整反馈数据帧，带超时控制
        '=====================================================================
        Private Function ASCII_WaitAckFrame(ByRef strBuffer As String) As UInt16
            Dim bytBuffer(0 To 1024) As Byte
            Dim intLength As UInt32 = 0

            Dim intTimeOut As UInt32 = 0

            Dim intTimeTick_pre As Integer = My.Computer.Clock.TickCount    '初始化定时

            Dim intStart As Int16 = 0
            Dim intEnd As Int16 = 0

            strBuffer = ""

            Try
                Do
                    Thread.Sleep(1)    '阻塞 1ms
                    intTimeOut = My.Computer.Clock.TickCount - intTimeTick_pre

                    '处理接收的数据
                    If PortLink.Available() > bytBuffer.Count Then
                        intLength = PortLink.ReadData(bytBuffer, bytBuffer.Count) '读取新数据
                    Else
                        intLength = PortLink.ReadData(bytBuffer, PortLink.Available()) '读取新数据
                    End If

                    strBuffer &= BytesArrayToString(bytBuffer, 0, intLength)

                    intStart = InStr(strBuffer, ":")
                    intEnd = InStr(strBuffer, vbCrLf)

                Loop Until (intStart > 0 And intEnd > 0) Or intTimeOut >= Reg.ResponseTime '帧起始 '帧结束 '超时

                If intTimeOut >= Reg.ResponseTime Then '超时
                    Debug.Print("ASCII Modbus timeout!") '程序不中断
                    blnModbusOK = False
                    Return 1
                End If

                strBuffer = Mid(strBuffer, intStart, intEnd - intStart + 4) '得到反馈帧字符串

                If Mid(strBuffer, strBuffer.Count - 3, 2) <> LRC(Mid(strBuffer, 2, strBuffer.Count - 1 - 4)) Then 'LRC校验错误
                    Debug.Print("ASCII Modbus LRC error!") '程序不中断
                    blnModbusOK = False
                    Return 2
                ElseIf (bytBuffer(1) And &H80) <> 0 Then   '设备反馈错误
                    Debug.Print("ASCII Modbus ack error!") '程序不中断
                    blnModbusOK = False
                    Return 4
                End If

                blnModbusOK = True
                Return 0
            Catch ex As Exception
                Debug.Print(ex.Message) '程序不中断
                blnModbusOK = False
                Return 3
            End Try
        End Function



        '=====================================================================
        '函 数 名: ASCII_IN_ReadRegisters
        '参    数: uiIndex = 索引
        '返 回 值: 
        '功能描述: ASCII遍历读 输入寄存器
        '=====================================================================
        Private Sub ASCII_IN_ReadRegisters(ByVal uiIndex As UInt16)
            Dim i As UInt16

            Try
                ClearInputBuffer() '清空输入缓冲区

                SyncLock (threadLock)   '线程互斥
                    Dim cmd As Byte '命令

                    If Reg.RegBlockIN(uiIndex).BitMode = False Then '字寄存器
                        cmd = CMD_READ_IN_REG
                    Else '位寄存器
                        cmd = CMD_READ_IN_BIT
                    End If

                    strTxDBuffer = ASCII_MakeCmdFrame(Reg.RegBlockIN(uiIndex).ID, cmd, Reg.RegBlockIN(uiIndex).StartAddr, Reg.RegBlockIN(uiIndex).EndAddr - Reg.RegBlockIN(uiIndex).StartAddr + 1)
                End SyncLock

                bytTxDBuffer = StringToBytesArray(strTxDBuffer)
                PortLink.WriteData(bytTxDBuffer, bytTxDBuffer.Count)    '发送ASCII命令帧

                If ASCII_WaitAckFrame(strRxDBuffer) = 0 Then '反馈帧 OK
                    If Reg.RegBlockIN(uiIndex).BitMode = False Then '字寄存器
                        '3A 30 31 30 34 | 31 34 | 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 45 38 0D 0A
                        If Mid(strRxDBuffer, 4, 2) = "04" Then
                            Dim start As UInt16

                            SyncLock (threadLock)   '线程互斥
                                For i = 0 To Reg.RegBlockIN(uiIndex).EndAddr - Reg.RegBlockIN(uiIndex).StartAddr
                                    start = 1 + 3 * 2 + 1 + i * 4

                                    If strRxDBuffer.Count >= start + 4 Then
                                        Reg.RegBlockIN(uiIndex).Item(i) = HexStringToUint16(Mid(strRxDBuffer, start, 4))
                                    End If
                                Next

                                Reg.RegBlockIN(uiIndex).TimerOutCount = 0
                            End SyncLock
                        End If
                    Else '位寄存器
                        'DEBUG（预留）
                    End If
                Else

                    SyncLock (threadLock)   '线程互斥
                        If Reg.RegBlockIN(uiIndex).TimerOutCount < &HFFFF Then
                            Reg.RegBlockIN(uiIndex).TimerOutCount += 1
                        End If
                    End SyncLock
                End If

                Thread.Sleep(Reg.IntervalTime)    '阻塞 modbus帧间隔

                'Debug.Print("IN:" & CStr(My.Computer.Clock.TickCount))
            Catch ex As Exception
                Debug.Print(ex.Message) '程序不中断
            End Try

            Return
        End Sub



        '=====================================================================
        '函 数 名: ASCII_OUT_ReadRegisters
        '参    数: uiIndex = 索引
        '返 回 值: 
        '功能描述: ASCII遍历读 输出寄存器
        '=====================================================================
        Private Sub ASCII_OUT_ReadRegisters(ByVal uiIndex As UInt16)
            Dim i As UInt16

            Try
                ClearInputBuffer() '清空输入缓冲区

                SyncLock (threadLock)   '线程互斥
                    Dim cmd As Byte '命令

                    If Reg.RegBlockOUT(uiIndex).BitMode = False Then '字寄存器
                        cmd = CMD_READ_OUT_REG
                    Else '位寄存器
                        cmd = CMD_READ_OUT_BIT
                    End If

                    strTxDBuffer = ASCII_MakeCmdFrame(Reg.RegBlockOUT(uiIndex).ID, cmd, Reg.RegBlockOUT(uiIndex).StartAddr, Reg.RegBlockOUT(uiIndex).EndAddr - Reg.RegBlockOUT(uiIndex).StartAddr + 1)
                End SyncLock

                bytTxDBuffer = StringToBytesArray(strTxDBuffer)
                PortLink.WriteData(bytTxDBuffer, bytTxDBuffer.Count)

                If ASCII_WaitAckFrame(strRxDBuffer) = 0 Then '反馈帧 OK
                    If Reg.RegBlockOUT(uiIndex).BitMode = False Then '字寄存器
                        '3A 30 31 30 33 | 31 33 | 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 45 38 0D 0A
                        If Mid(strRxDBuffer, 4, 2) = "03" Then
                            Dim start As UInt16

                            SyncLock (threadLock)   '线程互斥
                                For i = 0 To Reg.RegBlockOUT(uiIndex).EndAddr - Reg.RegBlockOUT(uiIndex).StartAddr
                                    start = 1 + 3 * 2 + 1 + i * 4

                                    If strRxDBuffer.Count >= start + 4 Then
                                        Reg.RegBlockOUT(uiIndex).Item(i) = HexStringToUint16(Mid(strRxDBuffer, start, 4))

                                        If Reg.RegBlockOUT(uiIndex).ItemNeedWrite(i) = False Then
                                            Reg.RegBlockOUT(uiIndex).ItemWrite(i) = Reg.RegBlockOUT(uiIndex).Item(i)
                                        End If
                                    End If
                                Next

                                Reg.RegBlockOUT(uiIndex).TimerOutCount = 0
                            End SyncLock
                        End If
                    Else '位寄存器
                        'DEBUG
                    End If
                Else
                    SyncLock (threadLock)   '线程互斥
                        If Reg.RegBlockOUT(uiIndex).TimerOutCount < &HFFFF Then
                            Reg.RegBlockOUT(uiIndex).TimerOutCount += 1
                        End If
                    End SyncLock
                End If

                Thread.Sleep(Reg.IntervalTime)    '阻塞 modbus帧间隔

                'Debug.Print("OUT:" & CStr(My.Computer.Clock.TickCount))
            Catch ex As Exception
                Debug.Print(ex.Message) '程序不中断
            End Try

            Return
        End Sub



        '=====================================================================
        '函 数 名: ASCII_OUT_WriteRegisters
        '参    数: uiIndex = 索引
        '返 回 值: 
        '功能描述: ASCII遍历写 输出寄存器
        '=====================================================================
        Private Sub ASCII_OUT_WriteRegisters(ByVal uiIndex As UInt16)
            Dim i As UInt16
            Dim num As UInt16
            Dim blnNeedSend As Boolean

            Try
                ClearInputBuffer() '清空输入缓冲区

                SyncLock (threadLock)   '线程互斥
                    num = Reg.RegBlockOUT(uiIndex).EndAddr - Reg.RegBlockOUT(uiIndex).StartAddr + 1
                End SyncLock

                For i = 0 To num - 1 '遍历寄存器
                    blnNeedSend = False

                    SyncLock (threadLock)   '线程互斥
                        If Reg.RegBlockOUT(uiIndex).ItemNeedWrite(i) = True Then
                            If Reg.RegBlockOUT(uiIndex).Item(i) <> Reg.RegBlockOUT(uiIndex).ItemWrite(i) Then
                                blnNeedSend = True

                                Dim cmd As Byte '命令

                                If Reg.RegBlockOUT(uiIndex).BitMode = False Then '字寄存器
                                    cmd = CMD_WRITE_OUT_REG
                                Else '位寄存器
                                    cmd = CMD_WRITE_OUT_BIT
                                End If

                                strTxDBuffer = ASCII_MakeCmdFrame(Reg.RegBlockOUT(uiIndex).ID, cmd, Reg.RegBlockOUT(uiIndex).StartAddr + i, Reg.RegBlockOUT(uiIndex).ItemWrite(i))
                            End If
                        End If
                    End SyncLock

                    If blnNeedSend = True Then
                        bytTxDBuffer = StringToBytesArray(strTxDBuffer)
                        PortLink.WriteData(bytTxDBuffer, bytTxDBuffer.Count)

                        If ASCII_WaitAckFrame(strRxDBuffer) = 0 Then '反馈帧 OK
                            If Reg.RegBlockOUT(uiIndex).BitMode = False Then '字寄存器
                                '3A 30 31 30 36 | 30 30 30 30 30 30 30 31 46 38 0D 0A
                                SyncLock (threadLock)   '线程互斥
                                    If Mid(strRxDBuffer, 4, 2) = "06" Then
                                        Reg.RegBlockOUT(uiIndex).TimerOutCount = 0
                                        Reg.RegBlockOUT(uiIndex).ItemNeedWrite(i) = False     '反馈成功取消发送，失败下次继续发送
                                    Else
                                        If Reg.RegBlockOUT(uiIndex).TimerOutCount < &HFFFF Then
                                            Reg.RegBlockOUT(uiIndex).TimerOutCount += 1
                                        End If
                                    End If
                                End SyncLock
                            Else '位寄存器
                                'DEBUG
                            End If
                        Else
                            SyncLock (threadLock)   '线程互斥
                                If Reg.RegBlockOUT(uiIndex).TimerOutCount < &HFFFF Then
                                    Reg.RegBlockOUT(uiIndex).TimerOutCount += 1
                                End If
                            End SyncLock
                        End If

                        Thread.Sleep(Reg.IntervalTime)    '阻塞 modbus帧间隔

                        'Debug.Print("OUT:" & CStr(My.Computer.Clock.TickCount))
                    End If
                Next
            Catch ex As Exception
                Debug.Print(ex.Message) '程序不中断
            End Try

            Return
        End Sub




        '=====================================================================
        '函 数 名: RTU_MakeCmdFrame
        '参    数: ID = 从机ID号
        '          ucCmd = 命令(寄存器命令4/3/6)（位命令2/1/5）
        '          uiAddr = 数据1
        '          uiData = 数据2
        '返 回 值: 帧数组
        '功能描述: 生成命令帧，双字数据型(命令+数据1+数据2)，帧长7字节
        '=====================================================================
        Private Function RTU_MakeCmdFrame(ByVal ID As Byte, ByVal ucCmd As Byte, ByVal uiAddr As UInt16, ByVal uiData As UInt16) As Byte()
            Dim str1(0 To 7) As Byte

            str1(0) = ID
            str1(1) = ucCmd
            str1(2) = uiAddr \ &H100
            str1(3) = uiAddr And &HFF

            If ucCmd = 5 Then  '单线圈输出
                If uiData <> 0 Then
                    str1(4) = &HFF
                    str1(5) = &H0
                Else
                    str1(4) = &H0
                    str1(5) = &H0
                End If
            Else  'ucCmd = 4/3/6,2/1
                str1(4) = uiData \ &H100
                str1(5) = uiData And &HFF
            End If

            Dim crc As Byte() = CRC16(str1, 0, 6)

            str1(6) = crc(1)
            str1(7) = crc(0)
            ModbusBase.BytesArrayToString(str1, 0, 8)
            Return str1
        End Function




        '=====================================================================
        '函 数 名: RTU_MakeXCmdFrame
        '参    数: ID = 从机ID号
        '          ucCmd = 命令(位命令15/寄存器命令16)
        '          uiAddr = 数据1
        '          uiData = 数组
        '返 回 值: 帧数组
        '功能描述: 生成命令帧，多字数据型(命令+数据1+数组)，帧长多字节
        '=====================================================================
        Private Function RTU_MakeXCmdFrame(ByVal ID As Byte, ByVal ucCmd As Byte, ByVal uiAddr As UInt16, ByVal uiData() As UInt16) As Byte()
            Dim str1 As Byte()
            Dim i As UInt16

            '计算空间
            If ucCmd = 15 Then  '多线圈输出
                If uiData.Count Mod 8 <> 0 Then
                    ReDim str1(0 To 7 + uiData.Count / 8 + 1 + 1)
                Else
                    ReDim str1(0 To 7 + uiData.Count / 8 + 1)
                End If
            Else
                ReDim str1(0 To 7 + uiData.Count * 2 + 1)
            End If

            str1(0) = ID
            str1(1) = ucCmd
            str1(2) = uiAddr \ &H100
            str1(3) = uiAddr And &HFF
            str1(4) = uiData.Count \ &H100
            str1(5) = uiData.Count And &HFF

            If ucCmd = 15 Then  '多线圈输出
                If uiData.Count Mod 8 <> 0 Then
                    str1(6) = uiData.Count / 8 + 1 '数据字节数
                Else
                    str1(6) = uiData.Count / 8 '数据字节数
                End If

                Dim index As UInt16 = 0
                Dim tmp As Byte = 0
                Dim mask As Byte = &H1

                For i = 0 To uiData.Count - 1
                    If uiData(i) <> 0 Then
                        tmp = tmp Or mask
                    End If

                    If mask = &H80 Then
                        str1(7 + index) = tmp
                        index += 1
                        tmp = 0
                        mask = &H1
                    Else
                        mask <<= 1
                    End If
                Next

            Else  'ucCmd = 16
                str1(6) = uiData.Count * 2 '数据字节数

                For i = 0 To uiData.Count - 1
                    str1(7 + i * 2) = uiData(i) \ &H100
                    str1(7 + i * 2 + 1) = uiData(i) And &HFF
                Next
            End If

            Dim crc As Byte() = CRC16(str1, 0, 7 + uiData.Count * 2)

            str1(7 + uiData.Count * 2) = crc(1)
            str1(7 + uiData.Count * 2 + 1) = crc(0)

            Return str1
        End Function




        '=====================================================================
        '函 数 名: RTU_WaitAckFrame
        '参    数: bytBuffer = 接收数据缓冲区
        '返 回 值: 0 = OK
        '          1 = 超时
        '          2 = 帧校验错误
        '          3 = 系统错误
        '          4 = 命令错误
        '功能描述: 等待节点的完整反馈数据帧，带超时控制
        '=====================================================================
        Private Function RTU_WaitAckFrame(ByRef bytBuffer As Byte()) As UInt16
            Dim intLength As UInt32 = 0
            Dim intTimeOut As UInt32 = 0

            Dim intTimeTick_pre As Integer = My.Computer.Clock.TickCount

            Try
                Do
                    Thread.Sleep(1)    '阻塞 1ms
                    intTimeOut = My.Computer.Clock.TickCount - intTimeTick_pre

                    If intLength <> PortLink.Available() Then
                        intTimeTick_pre = My.Computer.Clock.TickCount
                        intLength = PortLink.Available()
                    End If

                Loop Until (intTimeOut >= Reg.ResponseTime) Or (intLength > 0 And intTimeOut >= Reg.IntervalTime) '超时

                If intTimeOut >= Reg.ResponseTime Then '超时
                    Debug.Print("RTU Modbus timeout!") '程序不中断
                    blnModbusOK = False
                    Return 1
                End If

                ReDim bytBuffer(0 To PortLink.Available() - 1)
                intLength = PortLink.ReadData(bytBuffer, bytBuffer.Count) '读取新数据

                If intLength > 2 Then
                    Dim crc As Byte() = CRC16(bytBuffer, 0, intLength - 2)
                    If bytBuffer(intLength - 2) <> crc(1) Or bytBuffer(intLength - 1) <> crc(0) Then 'CRC校验错误
                        Debug.Print("RTU Modbus CRC error!") '程序不中断
                        blnModbusOK = False
                        Return 2
                    ElseIf (bytBuffer(1) And &H80) <> 0 Then   '设备反馈错误
                        Debug.Print("RTU Modbus ack error!") '程序不中断
                        blnModbusOK = False
                        Return 4
                    End If
                Else
                    Debug.Print("RTU Modbus length error!") '程序不中断
                    blnModbusOK = False
                    Return 2
                End If

                blnModbusOK = True
                Return 0
            Catch ex As Exception
                Debug.Print(ex.Message) '程序不中断
                blnModbusOK = False
                Return 3
            End Try
        End Function



        '=====================================================================
        '函 数 名: RTU_BitBufferToWordBuffer
        '参    数: bytBitBuffer = 位字节数据
        '          bytRegBuffer = 寄存器
        '返 回 值: 
        '功能描述: 读回位字节数据转换到寄存器，01/02命令使用
        '=====================================================================
        Private Sub RTU_BitBufferToWordBuffer(ByRef bytBitBuffer As Byte(), ByRef bytRegBuffer As UInt16())
            Dim p As UInt16 = 0
            Dim mask As UInt16 = &H1

            Dim i As UInt16

            For i = 0 To bytRegBuffer.Count - 1
                If p <= bytBitBuffer.Count - 1 Then
                    If (bytBitBuffer(p) And mask) <> 0 Then
                        bytRegBuffer(i) = 1
                    Else
                        bytRegBuffer(i) = 0
                    End If
                Else
                    bytRegBuffer(i) = 0
                End If

                mask *= 2   '向右移位
                If mask > &HFF Then
                    mask = &H1
                    p += 1
                End If
            Next
        End Sub



        '=====================================================================
        '函 数 名: RTU_IN_ReadRegisters
        '参    数: uiIndex = 索引
        '返 回 值: 
        '功能描述: RTU遍历读 输入寄存器
        '=====================================================================
        Private Sub RTU_IN_ReadRegisters(ByVal uiIndex As UInt16)
            Dim i As UInt16

            Try
                ClearInputBuffer() '清空输入缓冲区

                SyncLock (threadLock)   '线程互斥
                    Dim cmd As Byte '命令

                    If Reg.RegBlockIN(uiIndex).BitMode = False Then '字寄存器
                        cmd = CMD_READ_IN_REG
                    Else '位寄存器
                        cmd = CMD_READ_IN_BIT
                    End If

                    bytTxDBuffer = RTU_MakeCmdFrame(Reg.RegBlockIN(uiIndex).ID, cmd, Reg.RegBlockIN(uiIndex).StartAddr, Reg.RegBlockIN(uiIndex).EndAddr - Reg.RegBlockIN(uiIndex).StartAddr + 1)
                End SyncLock

                PortLink.WriteData(bytTxDBuffer, bytTxDBuffer.Count)

                If RTU_WaitAckFrame(bytRxDBuffer) = 0 Then '反馈帧 OK
                    If Reg.RegBlockIN(uiIndex).BitMode = False Then '字寄存器
                        '01 04 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 A3 67
                        If bytRxDBuffer(1) = CMD_READ_IN_REG Then
                            Dim start As UInt16

                            SyncLock (threadLock)   '线程互斥
                                For i = 0 To Reg.RegBlockIN(uiIndex).EndAddr - Reg.RegBlockIN(uiIndex).StartAddr
                                    start = 3 + i * 2

                                    If bytRxDBuffer.Count >= start + 1 Then
                                        Reg.RegBlockIN(uiIndex).Item(i) = CUInt(bytRxDBuffer(start)) * &H100 + CUInt(bytRxDBuffer(start + 1))
                                    End If
                                Next

                                Reg.RegBlockIN(uiIndex).TimerOutCount = 0
                            End SyncLock
                        End If

                    Else '位寄存器
                        '01 02 01 00 A1 88
                        If bytRxDBuffer(1) = CMD_READ_IN_BIT Then
                            Dim bytBuf(0 To bytRxDBuffer(2) - 1) As Byte

                            For i = 0 To bytRxDBuffer(2) - 1
                                bytBuf(i) = bytRxDBuffer(3 + i)
                            Next

                            SyncLock (threadLock)   '线程互斥
                                RTU_BitBufferToWordBuffer(bytBuf, Reg.RegBlockIN(uiIndex).Item) '读回位字节数据转换到寄存器，01/02命令使用
                                Reg.RegBlockIN(uiIndex).TimerOutCount = 0
                            End SyncLock
                        End If

                    End If
                Else
                    SyncLock (threadLock)   '线程互斥
                        If Reg.RegBlockIN(uiIndex).TimerOutCount < &HFFFF Then
                            Reg.RegBlockIN(uiIndex).TimerOutCount += 1
                        End If
                    End SyncLock
                End If

                Thread.Sleep(Reg.IntervalTime)    '阻塞 modbus帧间隔

                'Debug.Print("IN:" & CStr(My.Computer.Clock.TickCount))
            Catch ex As Exception
                Debug.Print(ex.Message) '程序不中断
            End Try

            Return
        End Sub



        '=====================================================================
        '函 数 名: RTU_OUT_ReadRegisters
        '参    数: uiIndex = 索引
        '返 回 值: 
        '功能描述: RTU遍历读 输出寄存器
        '=====================================================================
        Private Sub RTU_OUT_ReadRegisters(ByVal uiIndex As UInt16)
            Dim i As UInt16

            Try
                ClearInputBuffer() '清空输入缓冲区

                SyncLock (threadLock)   '线程互斥
                    Dim cmd As Byte '命令

                    If Reg.RegBlockOUT(uiIndex).BitMode = False Then '字寄存器
                        cmd = CMD_READ_OUT_REG
                    Else '位寄存器
                        cmd = CMD_READ_OUT_BIT
                    End If

                    bytTxDBuffer = RTU_MakeCmdFrame(Reg.RegBlockOUT(uiIndex).ID, cmd, Reg.RegBlockOUT(uiIndex).StartAddr, Reg.RegBlockOUT(uiIndex).EndAddr - Reg.RegBlockOUT(uiIndex).StartAddr + 1)
                End SyncLock

                PortLink.WriteData(bytTxDBuffer, bytTxDBuffer.Count)

                If RTU_WaitAckFrame(bytRxDBuffer) = 0 Then '反馈帧 OK
                    If Reg.RegBlockOUT(uiIndex).BitMode = False Then '字寄存器
                        '01 03 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 A3 67
                        If bytRxDBuffer(1) = CMD_READ_OUT_REG Then
                            Dim start As UInt16

                            SyncLock (threadLock)   '线程互斥
                                For i = 0 To Reg.RegBlockOUT(uiIndex).EndAddr - Reg.RegBlockOUT(uiIndex).StartAddr
                                    start = 3 + i * 2

                                    If bytRxDBuffer.Count >= start + 1 Then
                                        Reg.RegBlockOUT(uiIndex).Item(i) = CUInt(bytRxDBuffer(start)) * &H100 + CUInt(bytRxDBuffer(start + 1))

                                        If Reg.RegBlockOUT(uiIndex).ItemNeedWrite(i) = False Then
                                            Reg.RegBlockOUT(uiIndex).ItemWrite(i) = Reg.RegBlockOUT(uiIndex).Item(i)
                                        End If
                                    End If
                                Next

                                Reg.RegBlockOUT(uiIndex).TimerOutCount = 0
                            End SyncLock
                        End If

                    Else '位寄存器
                        '01 01 01 00 51 88
                        If bytRxDBuffer(1) = CMD_READ_OUT_BIT Then
                            Dim bytBuf(0 To bytRxDBuffer(2) - 1) As Byte

                            For i = 0 To bytRxDBuffer(2) - 1
                                bytBuf(i) = bytRxDBuffer(3 + i)
                            Next

                            SyncLock (threadLock)   '线程互斥
                                RTU_BitBufferToWordBuffer(bytBuf, Reg.RegBlockOUT(uiIndex).Item) '读回位字节数据转换到寄存器，01/02命令使用

                                For i = 0 To Reg.RegBlockOUT(uiIndex).EndAddr - Reg.RegBlockOUT(uiIndex).StartAddr
                                    If Reg.RegBlockOUT(uiIndex).ItemNeedWrite(i) = False Then
                                        Reg.RegBlockOUT(uiIndex).ItemWrite(i) = Reg.RegBlockOUT(uiIndex).Item(i)
                                    End If
                                Next

                                Reg.RegBlockOUT(uiIndex).TimerOutCount = 0
                            End SyncLock
                        End If

                    End If
                Else
                    SyncLock (threadLock)   '线程互斥
                        If Reg.RegBlockOUT(uiIndex).TimerOutCount < &HFFFF Then
                            Reg.RegBlockOUT(uiIndex).TimerOutCount += 1
                        End If
                    End SyncLock
                End If

                Thread.Sleep(Reg.IntervalTime)    '阻塞 modbus帧间隔

                'Debug.Print("OUT:" & CStr(My.Computer.Clock.TickCount))
            Catch ex As Exception
                Debug.Print(ex.Message) '程序不中断
            End Try

            Return
        End Sub




        '=====================================================================
        '函 数 名: RTU_OUT_WriteRegisters
        '参    数: uiIndex = 索引
        '返 回 值: 
        '功能描述: RTU遍历写 输出寄存器
        '=====================================================================
        Private Sub RTU_OUT_WriteRegisters(ByVal uiIndex As UInt16)
            Dim i As UInt16
            Dim num As UInt16
            Dim blnNeedSend As Boolean

            Try
                ClearInputBuffer() '清空输入缓冲区

                SyncLock (threadLock)   '线程互斥
                    num = Reg.RegBlockOUT(uiIndex).EndAddr - Reg.RegBlockOUT(uiIndex).StartAddr + 1
                End SyncLock

                If Reg.RegBlockOUT(uiIndex).XWriteMode = False Then  '单字/位寄存器写
                    For i = 0 To num - 1 '遍历寄存器
                        blnNeedSend = False

                        SyncLock (threadLock)   '线程互斥
                            If Reg.RegBlockOUT(uiIndex).ItemNeedWrite(i) = True Then
                                If Reg.RegBlockOUT(uiIndex).Item(i) <> Reg.RegBlockOUT(uiIndex).ItemWrite(i) Then
                                    blnNeedSend = True

                                    Dim cmd As Byte '命令

                                    If Reg.RegBlockOUT(uiIndex).BitMode = False Then '字寄存器
                                        cmd = CMD_WRITE_OUT_REG
                                    Else '位寄存器
                                        cmd = CMD_WRITE_OUT_BIT
                                    End If

                                    bytTxDBuffer = RTU_MakeCmdFrame(Reg.RegBlockOUT(uiIndex).ID, cmd, Reg.RegBlockOUT(uiIndex).StartAddr + i, Reg.RegBlockOUT(uiIndex).ItemWrite(i))
                                End If
                            End If
                        End SyncLock

                        If blnNeedSend = True Then
                            PortLink.WriteData(bytTxDBuffer, bytTxDBuffer.Count)

                            If RTU_WaitAckFrame(bytRxDBuffer) = 0 Then '反馈帧 OK
                                If Reg.RegBlockOUT(uiIndex).BitMode = False Then '字寄存器
                                    '01 06 00 00 00 01 48 0A
                                    SyncLock (threadLock)   '线程互斥
                                        If bytRxDBuffer(1) = CMD_WRITE_OUT_REG Then
                                            Reg.RegBlockOUT(uiIndex).TimerOutCount = 0
                                            Reg.RegBlockOUT(uiIndex).ItemNeedWrite(i) = False '写入成功(写入失败则下一次继续写入)
                                        Else
                                            If Reg.RegBlockOUT(uiIndex).TimerOutCount < &HFFFF Then
                                                Reg.RegBlockOUT(uiIndex).TimerOutCount += 1
                                            End If
                                        End If
                                    End SyncLock
                                Else '位寄存器
                                    '01 05 00 11 FF 00 DC 3F
                                    SyncLock (threadLock)   '线程互斥
                                        If bytRxDBuffer(1) = CMD_WRITE_OUT_BIT Then
                                            Reg.RegBlockOUT(uiIndex).TimerOutCount = 0
                                            Reg.RegBlockOUT(uiIndex).ItemNeedWrite(i) = False '写入成功(写入失败则下一次继续写入)
                                        Else
                                            If Reg.RegBlockOUT(uiIndex).TimerOutCount < &HFFFF Then
                                                Reg.RegBlockOUT(uiIndex).TimerOutCount += 1
                                            End If
                                        End If
                                    End SyncLock
                                End If
                            Else
                                SyncLock (threadLock)   '线程互斥
                                    If Reg.RegBlockOUT(uiIndex).TimerOutCount < &HFFFF Then
                                        Reg.RegBlockOUT(uiIndex).TimerOutCount += 1
                                    End If
                                End SyncLock
                            End If

                            Thread.Sleep(Reg.IntervalTime)    '阻塞 modbus帧间隔

                            'Debug.Print("W:" & CStr(My.Computer.Clock.TickCount))
                        End If
                    Next

                Else '多字/位寄存器写
                    blnNeedSend = False

                    SyncLock (threadLock)   '线程互斥
                        For i = 0 To num - 1 '遍历寄存器
                            If Reg.RegBlockOUT(uiIndex).ItemNeedWrite(i) = True Then
                                If Reg.RegBlockOUT(uiIndex).Item(i) <> Reg.RegBlockOUT(uiIndex).ItemWrite(i) Then
                                    blnNeedSend = True
                                End If
                            End If
                        Next
                    End SyncLock

                    If blnNeedSend Then
                        Dim cmd As Byte '命令

                        If Reg.RegBlockOUT(uiIndex).BitMode = False Then '字寄存器
                            cmd = CMD_WRITE_OUT_REGS
                        Else '位寄存器
                            cmd = CMD_WRITE_OUT_BITS
                        End If

                        bytTxDBuffer = RTU_MakeXCmdFrame(Reg.RegBlockOUT(uiIndex).ID, cmd, Reg.RegBlockOUT(uiIndex).StartAddr, Reg.RegBlockOUT(uiIndex).ItemWrite)

                        PortLink.WriteData(bytTxDBuffer, bytTxDBuffer.Count)

                        If RTU_WaitAckFrame(bytRxDBuffer) = 0 Then '反馈帧 OK
                            If Reg.RegBlockOUT(uiIndex).BitMode = False Then '字寄存器
                                '01 10 ******
                                SyncLock (threadLock)   '线程互斥
                                    If bytRxDBuffer(1) = CMD_WRITE_OUT_REGS Then
                                        For i = 0 To num - 1 '遍历寄存器
                                            Reg.RegBlockOUT(uiIndex).TimerOutCount = 0
                                            Reg.RegBlockOUT(uiIndex).ItemNeedWrite(i) = False '写入成功(写入失败则下一次继续写入)
                                        Next
                                    Else
                                        For i = 0 To num - 1 '遍历寄存器
                                            If Reg.RegBlockOUT(uiIndex).TimerOutCount < &HFFFF Then
                                                Reg.RegBlockOUT(uiIndex).TimerOutCount += 1
                                            End If
                                        Next
                                    End If
                                End SyncLock
                            Else '位寄存器
                                '01 0A ******
                                SyncLock (threadLock)   '线程互斥
                                    If bytRxDBuffer(1) = CMD_WRITE_OUT_BITS Then
                                        For i = 0 To num - 1 '遍历寄存器
                                            Reg.RegBlockOUT(uiIndex).TimerOutCount = 0
                                            Reg.RegBlockOUT(uiIndex).ItemNeedWrite(i) = False '写入成功(写入失败则下一次继续写入)
                                        Next
                                    Else
                                        For i = 0 To num - 1 '遍历寄存器
                                            If Reg.RegBlockOUT(uiIndex).ItemNeedWrite(i) Then
                                                If Reg.RegBlockOUT(uiIndex).TimerOutCount < &HFFFF Then
                                                    Reg.RegBlockOUT(uiIndex).TimerOutCount += 1
                                                End If
                                            End If
                                        Next
                                    End If
                                End SyncLock
                            End If
                        Else
                            SyncLock (threadLock)   '线程互斥
                                For i = 0 To num - 1 '遍历寄存器
                                    If Reg.RegBlockOUT(uiIndex).ItemNeedWrite(i) Then
                                        If Reg.RegBlockOUT(uiIndex).TimerOutCount < &HFFFF Then
                                            Reg.RegBlockOUT(uiIndex).TimerOutCount += 1
                                        End If
                                    End If
                                Next
                            End SyncLock
                        End If

                        Thread.Sleep(Reg.IntervalTime)    '阻塞 modbus帧间隔

                        'Debug.Print("W:" & CStr(My.Computer.Clock.TickCount))

                    End If
                End If

            Catch ex As Exception
                Debug.Print(ex.Message) '程序不中断
            End Try

            Return
        End Sub




        '=====================================================================
        '函 数 名: TCP_MakeCmdFrame
        '参    数: ID = 从机ID号
        '          ucCmd = 命令
        '          uiAddr = 数据1
        '          uiData = 数据2
        '返 回 值: 帧数组
        '功能描述: 生成命令帧，双字数据型
        '=====================================================================
        Private Function TCP_MakeCmdFrame(ByVal ID As Byte, ByVal ucCmd As Byte, ByVal uiAddr As UInt16, ByVal uiData As UInt16) As Byte()
            Dim str1(0 To 11) As Byte
            Dim num As UInt16

            uiFrameID += 1
            If uiFrameID >= &HFFFF Then
                uiFrameID = 0
            End If

            num = 6

            str1(0) = uiFrameID \ &H100
            str1(1) = uiFrameID And &HFF
            str1(2) = 0
            str1(3) = 0
            str1(4) = num \ &H100
            str1(5) = num And &HFF

            str1(6) = ID
            str1(7) = ucCmd
            str1(8) = uiAddr \ &H100
            str1(9) = uiAddr And &HFF
            str1(10) = uiData \ &H100
            str1(11) = uiData And &HFF

            Return str1
        End Function




        '=====================================================================
        '函 数 名: TCP_WaitAckFrame
        '参    数: bytBuffer = 接收数据缓冲区
        '返 回 值: 0 = OK
        '          1 = 超时
        '          2 = 帧校验错误
        '          3 = 系统错误
        '          4 = 命令错误
        '功能描述: 等待节点的完整反馈数据帧，带超时控制
        '=====================================================================
        Private Function TCP_WaitAckFrame(ByRef bytBuffer As Byte()) As UInt16
            Dim intLength As UInt32 = 0
            Dim intTimeOut As UInt32 = 0

            Dim intTimeTick_pre As Integer = My.Computer.Clock.TickCount

            Try
                Do
                    Thread.Sleep(1)    '阻塞 1ms
                    intTimeOut = My.Computer.Clock.TickCount - intTimeTick_pre

                    If intLength <> PortLink.Available() Then
                        intTimeTick_pre = My.Computer.Clock.TickCount
                        intLength = PortLink.Available()
                    End If

                Loop Until (intTimeOut >= Reg.ResponseTime) Or (intLength > 0 And intTimeOut >= Reg.IntervalTime) '超时

                If intTimeOut >= Reg.ResponseTime Then '超时
                    Debug.Print("TCP Modbus timeout!") '程序不中断
                    blnModbusOK = False
                    Return 1
                End If

                ReDim bytBuffer(0 To PortLink.Available() - 1)
                intLength = PortLink.ReadData(bytBuffer, bytBuffer.Count) '读取新数据

                If intLength > 8 Then
                    If (CUInt(bytBuffer(0)) * &H100 + CUInt(bytBuffer(1))) <> uiFrameID Then    '帧号错误
                        Debug.Print("TCP Modbus 帧号 error!") '程序不中断
                        blnModbusOK = False
                        Return 2
                    ElseIf bytBuffer(2) <> 0 Or bytBuffer(3) <> 0 Then 'Modbus标识错误
                        Debug.Print("TCP Modbus 标识 error!") '程序不中断
                        blnModbusOK = False
                        Return 2
                    ElseIf (CUInt(bytBuffer(4)) * &H100 + CUInt(bytBuffer(5))) <> (intLength - 6) Then
                        blnModbusOK = False
                        Return 2
                    ElseIf (bytBuffer(7) And &H80) <> 0 Then    '设备反馈错误
                        Debug.Print("TCP Modbus ack error!") '程序不中断
                        blnModbusOK = False
                        Return 4
                    End If
                Else
                    Debug.Print("RTU Modbus length error!") '程序不中断
                    blnModbusOK = False
                    Return 2
                End If

                blnModbusOK = True
                Return 0
            Catch ex As Exception
                Debug.Print(ex.Message) '程序不中断
                blnModbusOK = False
                Return 3 '不中断进程
            End Try
        End Function



        '=====================================================================
        '函 数 名: TCP_IN_ReadRegisters
        '参    数: uiIndex = 索引
        '返 回 值: 
        '功能描述: TCP遍历读 输入寄存器
        '=====================================================================
        Private Sub TCP_IN_ReadRegisters(ByVal uiIndex As UInt16)
            Dim i As UInt16

            Try
                ClearInputBuffer() '清空输入缓冲区

                SyncLock (threadLock)   '线程互斥
                    Dim cmd As Byte '命令

                    If Reg.RegBlockIN(uiIndex).BitMode = False Then '字寄存器
                        cmd = CMD_READ_IN_REG
                    Else '位寄存器
                        cmd = CMD_READ_IN_BIT
                    End If

                    bytTxDBuffer = TCP_MakeCmdFrame(Reg.RegBlockIN(uiIndex).ID, cmd, Reg.RegBlockIN(uiIndex).StartAddr, Reg.RegBlockIN(uiIndex).EndAddr - Reg.RegBlockIN(uiIndex).StartAddr + 1)
                End SyncLock

                PortLink.WriteData(bytTxDBuffer, bytTxDBuffer.Count)

                If TCP_WaitAckFrame(bytRxDBuffer) = 0 Then '反馈帧 OK
                    If Reg.RegBlockIN(uiIndex).BitMode = False Then '字寄存器
                        '00 04 00 00 00 2B | 01 04 28 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
                        If bytRxDBuffer(7) = 4 Then '读输入寄存器
                            If bytRxDBuffer(8) = bytRxDBuffer.Count - 9 Then
                                Dim start As UInt16

                                SyncLock (threadLock)   '线程互斥
                                    For i = 0 To Reg.RegBlockIN(uiIndex).EndAddr - Reg.RegBlockIN(uiIndex).StartAddr
                                        start = 9 + i * 2

                                        If bytRxDBuffer.Count >= start + 1 Then
                                            Reg.RegBlockIN(uiIndex).Item(i) = CUInt(bytRxDBuffer(start)) * &H100 + CUInt(bytRxDBuffer(start + 1))
                                        End If
                                    Next

                                    Reg.RegBlockIN(uiIndex).TimerOutCount = 0
                                End SyncLock
                            End If
                        End If
                    Else '位寄存器
                        'debug
                    End If
                Else
                    SyncLock (threadLock)   '线程互斥
                        If Reg.RegBlockIN(uiIndex).TimerOutCount < &HFFFF Then
                            Reg.RegBlockIN(uiIndex).TimerOutCount += 1
                        End If
                    End SyncLock
                End If

                Thread.Sleep(Reg.IntervalTime)    '阻塞 modbus帧间隔

                'Debug.Print("IN:" & CStr(My.Computer.Clock.TickCount))
            Catch ex As Exception
                Debug.Print(ex.Message) '程序不中断
            End Try

            Return
        End Sub



        '=====================================================================
        '函 数 名: TCP_OUT_ReadRegisters
        '参    数: uiIndex = 索引
        '返 回 值: 
        '功能描述: TCP遍历读 输出寄存器
        '=====================================================================
        Private Sub TCP_OUT_ReadRegisters(ByVal uiIndex As UInt16)
            Dim i As UInt16

            Try
                ClearInputBuffer() '清空输入缓冲区

                SyncLock (threadLock)   '线程互斥
                    Dim cmd As Byte '命令

                    If Reg.RegBlockOUT(uiIndex).BitMode = False Then '字寄存器
                        cmd = CMD_READ_OUT_REG
                    Else '位寄存器
                        cmd = CMD_READ_OUT_BIT
                    End If

                    bytTxDBuffer = TCP_MakeCmdFrame(Reg.RegBlockOUT(uiIndex).ID, cmd, Reg.RegBlockOUT(uiIndex).StartAddr, Reg.RegBlockOUT(uiIndex).EndAddr - Reg.RegBlockOUT(uiIndex).StartAddr + 1)
                End SyncLock

                PortLink.WriteData(bytTxDBuffer, bytTxDBuffer.Count)

                If TCP_WaitAckFrame(bytRxDBuffer) = 0 Then '反馈帧 OK
                    If Reg.RegBlockOUT(uiIndex).BitMode = False Then '字寄存器
                        '00 04 00 00 00 2B | 01 03 28 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
                        If bytRxDBuffer(7) = 3 Then
                            If bytRxDBuffer(8) = bytRxDBuffer.Count - 9 Then
                                Dim start As UInt16

                                SyncLock (threadLock)   '线程互斥
                                    For i = 0 To Reg.RegBlockOUT(uiIndex).EndAddr - Reg.RegBlockOUT(uiIndex).StartAddr
                                        start = 9 + i * 2

                                        If bytRxDBuffer.Count >= start + 1 Then
                                            Reg.RegBlockOUT(uiIndex).Item(i) = CUInt(bytRxDBuffer(start)) * &H100 + CUInt(bytRxDBuffer(start + 1))

                                            If Reg.RegBlockOUT(uiIndex).ItemNeedWrite(i) = False Then
                                                Reg.RegBlockOUT(uiIndex).ItemWrite(i) = Reg.RegBlockOUT(uiIndex).Item(i)
                                            End If
                                        End If
                                    Next
                                    Reg.RegBlockOUT(uiIndex).TimerOutCount = 0
                                End SyncLock
                            End If
                        End If
                    Else '位寄存器
                        'DEBUG
                    End If
                Else
                    SyncLock (threadLock)   '线程互斥
                        If Reg.RegBlockOUT(uiIndex).TimerOutCount < &HFFFF Then
                            Reg.RegBlockOUT(uiIndex).TimerOutCount += 1
                        End If
                    End SyncLock
                End If

                Thread.Sleep(Reg.IntervalTime)    '阻塞 modbus帧间隔

                'Debug.Print("OUT:" & CStr(My.Computer.Clock.TickCount))
            Catch ex As Exception
                Debug.Print(ex.Message) '程序不中断
            End Try

            Return
        End Sub




        '=====================================================================
        '函 数 名: TCP_OUT_WriteRegisters
        '参    数: uiIndex = 索引
        '返 回 值: 
        '功能描述: TCP遍历写 输出寄存器
        '=====================================================================
        Private Sub TCP_OUT_WriteRegisters(ByVal uiIndex As UInt16)
            Dim i As UInt16
            Dim num As UInt16
            Dim blnNeedSend As Boolean

            Try
                ClearInputBuffer() '清空输入缓冲区

                SyncLock (threadLock)   '线程互斥
                    num = Reg.RegBlockOUT(uiIndex).EndAddr - Reg.RegBlockOUT(uiIndex).StartAddr + 1
                End SyncLock

                For i = 0 To num - 1 '遍历寄存器
                    blnNeedSend = False

                    SyncLock (threadLock)   '线程互斥
                        If Reg.RegBlockOUT(uiIndex).ItemNeedWrite(i) = True Then
                            If Reg.RegBlockOUT(uiIndex).Item(i) <> Reg.RegBlockOUT(uiIndex).ItemWrite(i) Then
                                blnNeedSend = True

                                Dim cmd As Byte '命令

                                If Reg.RegBlockOUT(uiIndex).BitMode = False Then '字寄存器
                                    cmd = CMD_WRITE_OUT_REG
                                Else '位寄存器
                                    cmd = CMD_WRITE_OUT_BIT
                                End If

                                bytTxDBuffer = TCP_MakeCmdFrame(Reg.RegBlockOUT(uiIndex).ID, cmd, Reg.RegBlockOUT(uiIndex).StartAddr + i, Reg.RegBlockOUT(uiIndex).ItemWrite(i))
                            End If
                        End If
                    End SyncLock

                    If blnNeedSend = True Then
                        PortLink.WriteData(bytTxDBuffer, bytTxDBuffer.Count)
                        If TCP_WaitAckFrame(bytRxDBuffer) = 0 Then '反馈帧 OK
                            If Reg.RegBlockOUT(uiIndex).BitMode = False Then '字寄存器
                                '00 04 00 00 00 2B | 01 04 00 00 00 01
                                SyncLock (threadLock)   '线程互斥
                                    If bytRxDBuffer(7) = 6 Then
                                        Reg.RegBlockOUT(uiIndex).TimerOutCount = 0
                                        Reg.RegBlockOUT(uiIndex).ItemNeedWrite(i) = False '写入成功(写入失败则下次继续写入)
                                    Else
                                        If Reg.RegBlockOUT(uiIndex).TimerOutCount < &HFFFF Then
                                            Reg.RegBlockOUT(uiIndex).TimerOutCount += 1
                                        End If
                                    End If
                                End SyncLock
                            Else '位寄存器
                                'DEBUG
                            End If
                        Else
                            SyncLock (threadLock)   '线程互斥
                                If Reg.RegBlockOUT(uiIndex).TimerOutCount < &HFFFF Then
                                    Reg.RegBlockOUT(uiIndex).TimerOutCount += 1
                                End If
                            End SyncLock
                        End If

                        Thread.Sleep(Reg.IntervalTime)    '阻塞 modbus帧间隔

                        'Debug.Print("OUT:" & CStr(My.Computer.Clock.TickCount))
                    End If
                Next
            Catch ex As Exception
                Debug.Print(ex.Message) '程序不中断
            End Try

            Return
        End Sub




        '=====================================================================
        '函 数 名:Thread_Timer
        '参    数:
        '返 回 值:
        '功能描述:通讯处理线程（巡检）
        '=====================================================================
        Private Sub Thread_Timer()
            Dim num As UInt16
            Dim i As UInt16

            strRxDBuffer = ""
            strTxDBuffer = ""
            Thread.Sleep(Reg.IntervalTime)    '阻塞 modbus帧间隔

            While True
                SyncLock (threadLock)   '线程互斥
                    num = Reg.RegBlockIN.Count
                End SyncLock

                '遍历读 输入寄存器
                For i = 0 To num - 1 '遍历寄存器片区
                    If Reg.RegBlockIN(i).Enable And Reg.RegBlockIN(i).ID > 0 Then '使能 and ID>0
                        If Reg.Protocol = "ASCII" Then
                            ASCII_IN_ReadRegisters(i)
                        ElseIf Reg.Protocol = "RTU" Then
                            RTU_IN_ReadRegisters(i)
                        ElseIf Reg.Protocol = "TCP" Then
                            TCP_IN_ReadRegisters(i)
                        End If
                    End If
                Next

                SyncLock (threadLock)   '线程互斥
                    num = Reg.RegBlockOUT.Count
                End SyncLock

                '遍历读 输出寄存器
                For i = 0 To num - 1 '遍历寄存器片区
                    If Reg.RegBlockOUT(i).Enable And Reg.RegBlockOUT(i).ID > 0 Then '使能 and ID>0
                        If Reg.Protocol = "ASCII" Then
                            ASCII_OUT_ReadRegisters(i)
                        ElseIf Reg.Protocol = "RTU" Then
                            RTU_OUT_ReadRegisters(i)
                        ElseIf Reg.Protocol = "TCP" Then
                            TCP_OUT_ReadRegisters(i)
                        End If
                    End If
                Next

                '遍历写 输出寄存器
                For i = 0 To num - 1 '遍历寄存器片区
                    If Reg.RegBlockOUT(i).Enable And Reg.RegBlockOUT(i).ID > 0 Then '使能 and ID>0
                        If Reg.Protocol = "ASCII" Then
                            ASCII_OUT_WriteRegisters(i)
                        ElseIf Reg.Protocol = "RTU" Then
                            RTU_OUT_WriteRegisters(i)
                        ElseIf Reg.Protocol = "TCP" Then
                            TCP_OUT_WriteRegisters(i)
                        End If
                    End If
                Next

            End While
        End Sub



        '=====================================================================
        '函 数 名:IN_IsBlockAvailable
        '参    数:
        '返 回 值:True/False
        '功能描述:输入寄存器块是否有效，【无线程保护】
        '=====================================================================
        Public Function IN_IsBlockAvailable() As Boolean
            If Not IsNothing(Reg) Then
                If Not IsNothing(Reg.RegBlockIN) Then
                    If Reg.RegBlockIN.Count > 0 Then
                        Return True
                    End If
                End If
            End If

            Return False
        End Function



        '=====================================================================
        '函 数 名:OUT_IsBlockAvailable
        '参    数:
        '返 回 值:True/False
        '功能描述:输出寄存器块是否有效，【无线程保护】
        '=====================================================================
        Public Function OUT_IsBlockAvailable() As Boolean
            If Not IsNothing(Reg) Then
                If Not IsNothing(Reg.RegBlockOUT) Then
                    If Reg.RegBlockOUT.Count > 0 Then
                        Return True
                    End If
                End If
            End If

            Return False
        End Function




        '=====================================================================
        '函 数 名:IN_TimeOut
        '参    数:ID = 从机ID号
        '         uiAddr = 寄存器地址
        '返 回 值:超时次数
        '功能描述:输入寄存器的超时次数
        '=====================================================================
        Public Function IN_TimeOut(ByVal ID As Byte, ByVal uiAddr As UInt16) As UInt16
            SyncLock (threadLock)   '线程互斥
                If IN_IsBlockAvailable() = True Then '输入寄存器块有效
                    Dim i As UInt16

                    For i = 0 To Reg.RegBlockIN.Count - 1  '遍历块
                        If Reg.RegBlockIN(i).ID = ID Then
                            If uiAddr >= Reg.RegBlockIN(i).StartAddr And uiAddr <= Reg.RegBlockIN(i).EndAddr Then
                                Return Reg.RegBlockIN(i).TimerOutCount
                            End If
                        End If
                    Next
                End If
            End SyncLock

            Return &HFFFF
        End Function




        '=====================================================================
        '函 数 名:IN_ReadUint16
        '参    数:ID = 从机ID号
        '         uiAddr = 寄存器地址
        '返 回 值:寄存器的内容（UInt16）（连接为打开或错误时返回0）
        '功能描述:从server中读取一个输入寄存器的内容（UInt16）
        '=====================================================================
        Public Function IN_ReadUint16(ByVal ID As Byte, ByVal uiAddr As UInt16) As UInt16
            SyncLock (threadLock)   '线程互斥
                If IN_IsBlockAvailable() = True Then '输入寄存器块有效
                    Dim i As UInt16

                    For i = 0 To Reg.RegBlockIN.Count - 1  '遍历块
                        If Reg.RegBlockIN(i).ID = ID Then
                            If uiAddr >= Reg.RegBlockIN(i).StartAddr And uiAddr <= Reg.RegBlockIN(i).EndAddr Then
                                Return Reg.RegBlockIN(i).Item(uiAddr - Reg.RegBlockIN(i).StartAddr)
                            End If
                        End If
                    Next
                End If
            End SyncLock

            Return 0
        End Function




        '=====================================================================
        '函 数 名:IN_ReadInt16
        '参    数:ID = 从机ID号
        '         uiAddr = 寄存器地址 
        '返 回 值:寄存器的内容（Int16）（连接为打开或错误时返回0）
        '功能描述:从server中读取1个输入寄存器的内容（Int16）
        '=====================================================================
        Public Function IN_ReadInt16(ByVal ID As Byte, ByVal uiAddr As UInt16) As Int16
            Return ModbusBase.Uint16ToInt16(IN_ReadUint16(ID, uiAddr))
        End Function




        '=====================================================================
        '函 数 名:IN_ReadUint32
        '参    数:ID = 从机ID号
        '         uiAddr = 寄存器地址 
        '返 回 值:寄存器的内容（UInt32）（连接为打开或错误时返回0）
        '功能描述:从server中读取2个输入寄存器的内容（UInt32）
        '=====================================================================
        Public Function IN_ReadUint32(ByVal ID As Byte, ByVal uiAddr As UInt16) As UInt32
            Return CULng(IN_ReadUint16(ID, uiAddr + 1)) * &H10000 + CULng(IN_ReadUint16(ID, uiAddr))
        End Function




        '=====================================================================
        '函 数 名:IN_ReadInt32
        '参    数:ID = 从机ID号
        '         uiAddr = 寄存器地址 
        '返 回 值:寄存器的内容（Int32）（连接为打开或错误时返回0）
        '功能描述:从server中读取2个输入寄存器的内容（Int32）
        '=====================================================================
        Public Function IN_ReadInt32(ByVal ID As Byte, ByVal uiAddr As UInt16) As Int32
            Return ModbusBase.Uint32ToInt32(IN_ReadUint32(ID, uiAddr))
        End Function




        '=====================================================================
        '函 数 名:IN_ReadSingle
        '参    数:ID = 从机ID号
        '         uiAddr = 寄存器地址 
        '返 回 值:寄存器的内容（Single）（连接为打开或错误时返回0）
        '功能描述:从server中读取2个输入寄存器的内容（Single）
        '=====================================================================
        Public Function IN_ReadSingle(ByVal ID As Byte, ByVal uiAddr As UInt16) As Single
            Return ModbusBase.Uint32ToSingle(IN_ReadUint32(ID, uiAddr))
        End Function




        '=====================================================================
        '函 数 名:IN_ReadString
        '参    数:ID = 从机ID号
        '         uiAddr = 寄存器地址 
        '         uiNum = 字节数量（2-N）
        '返 回 值:寄存器的内容（String）（连接为打开或错误时返回0）
        '功能描述:从server中读取输入寄存器的内容（String）
        '=====================================================================
        Public Function IN_ReadString(ByVal ID As Byte, ByVal uiAddr As UInt16, ByVal uiNum As UInt16)
            Dim str1 As String
            Dim tmp As UInt16
            Dim H, L As Byte
            Dim i As UInt16

            str1 = ""
            For i = uiAddr To uiAddr + uiNum \ 2 - 1
                tmp = IN_ReadUint16(ID, i)
                L = CByte(tmp Mod &H100)
                H = CByte(tmp \ &H100)
                If L > 127 Then
                    str1 &= ""
                Else
                    str1 &= Chr(L)
                End If
                If H > 127 Then
                    str1 &= ""
                Else
                    str1 &= Chr(H)
                End If
            Next

            If (uiNum And &H1) = &H1 Then    '不是整数字（单数字节）
                i = uiAddr + uiNum \ 2
                tmp = tmp = IN_ReadUint16(ID, i)

                If tmp <> 0 Then
                    L = tmp And &HFF
                    If L > 127 Then
                        str1 &= ""
                    Else
                        str1 &= Chr(L)
                    End If
                End If
            End If
            Return str1
        End Function




        '=====================================================================
        '函 数 名:IN_Enable
        '参    数:ID = 从机ID号
        '         uiAddr = 寄存器地址 
        '返 回 值:
        '功能描述:使能输入寄存器的片区有效性
        '=====================================================================
        Public Sub IN_Enable(ByVal ID As Byte, ByVal uiAddr As UInt16)
            SyncLock (threadLock)   '线程互斥
                If IN_IsBlockAvailable() = True Then '输入寄存器块有效
                    Dim i As UInt16

                    For i = 0 To Reg.RegBlockIN.Count - 1  '遍历块
                        If Reg.RegBlockIN(i).ID = ID Then
                            If uiAddr >= Reg.RegBlockIN(i).StartAddr And uiAddr <= Reg.RegBlockIN(i).EndAddr Then
                                Reg.RegBlockIN(i).Enable = True
                            End If
                        End If
                    Next
                End If
            End SyncLock
        End Sub




        '=====================================================================
        '函 数 名:IN_Disable
        '参    数:ID = 从机ID号
        '         uiAddr = 寄存器地址 
        '返 回 值:
        '功能描述:禁用输入寄存器的片区有效性
        '=====================================================================
        Public Sub IN_Disable(ByVal ID As Byte, ByVal uiAddr As UInt16)
            SyncLock (threadLock)   '线程互斥
                If IN_IsBlockAvailable() = True Then '输入寄存器块有效
                    Dim i As UInt16

                    For i = 0 To Reg.RegBlockIN.Count - 1  '遍历块
                        If Reg.RegBlockIN(i).ID = ID Then
                            If uiAddr >= Reg.RegBlockIN(i).StartAddr And uiAddr <= Reg.RegBlockIN(i).EndAddr Then
                                Reg.RegBlockIN(i).Enable = False
                            End If
                        End If
                    Next
                End If
            End SyncLock
        End Sub




        '=====================================================================
        '函 数 名:IN_Enable
        '参    数:ID = 从机ID号
        '         uiAddr = 寄存器地址 
        '返 回 值:当前是否使能
        '功能描述:检查输入寄存器的片区使能状态
        '=====================================================================
        Public Function IN_IsEnable(ByVal ID As Byte, ByVal uiAddr As UInt16) As Boolean
            SyncLock (threadLock)   '线程互斥
                If IN_IsBlockAvailable() = True Then '输入寄存器块有效
                    Dim i As UInt16

                    For i = 0 To Reg.RegBlockIN.Count - 1  '遍历块
                        If Reg.RegBlockIN(i).ID = ID Then
                            If uiAddr >= Reg.RegBlockIN(i).StartAddr And uiAddr <= Reg.RegBlockIN(i).EndAddr Then
                                Return Reg.RegBlockIN(i).Enable
                            End If
                        End If
                    Next
                End If
            End SyncLock

            Return False
        End Function




        '=====================================================================
        '函 数 名:OUT_TimeOut
        '参    数:ID = 从机ID号
        '         uiAddr = 寄存器地址
        '返 回 值:超时次数
        '功能描述:输出寄存器的超时次数
        '=====================================================================
        Public Function OUT_TimeOut(ByVal ID As Byte, ByVal uiAddr As UInt16) As UInt16
            SyncLock (threadLock)   '线程互斥
                If OUT_IsBlockAvailable() = True Then '输出寄存器块有效
                    Dim i As UInt16

                    For i = 0 To Reg.RegBlockOUT.Count - 1  '遍历块
                        If Reg.RegBlockOUT(i).ID = ID Then
                            If uiAddr >= Reg.RegBlockOUT(i).StartAddr And uiAddr <= Reg.RegBlockOUT(i).EndAddr Then
                                Return Reg.RegBlockOUT(i).TimerOutCount
                            End If
                        End If
                    Next
                End If
            End SyncLock

            Return &HFFFF
        End Function




        '=====================================================================
        '函 数 名:OUT_ReadUint16
        '参    数:ID = 从机ID号
        '         uiAddr = 寄存器地址
        '返 回 值:寄存器的内容（UInt16）（连接为打开或错误时返回0）
        '功能描述:从server中读取一个输出寄存器的内容（UInt16）
        '=====================================================================
        Public Function OUT_ReadUint16(ByVal ID As Byte, ByVal uiAddr As UInt16) As UInt16
            SyncLock (threadLock)   '线程互斥
                If OUT_IsBlockAvailable() = True Then '输出寄存器块有效
                    Dim i As UInt16

                    For i = 0 To Reg.RegBlockOUT.Count - 1  '遍历块
                        If Reg.RegBlockOUT(i).ID = ID Then
                            If uiAddr >= Reg.RegBlockOUT(i).StartAddr And uiAddr <= Reg.RegBlockOUT(i).EndAddr Then
                                Return Reg.RegBlockOUT(i).Item(uiAddr - Reg.RegBlockOUT(i).StartAddr)
                            End If
                        End If
                    Next
                End If
            End SyncLock

            Return 0
        End Function




        '=====================================================================
        '函 数 名:OUT_ReadInt16
        '参    数:ID = 从机ID号
        '         uiAddr = 寄存器地址 
        '返 回 值:寄存器的内容（Int16）（连接为打开或错误时返回0）
        '功能描述:从server中读取1个输出寄存器的内容（Int16）
        '=====================================================================
        Public Function OUT_ReadInt16(ByVal ID As Byte, ByVal uiAddr As UInt16) As Int16
            Return ModbusBase.Uint16ToInt16(OUT_ReadUint16(ID, uiAddr))
        End Function




        '=====================================================================
        '函 数 名:OUT_ReadUint32
        '参    数:ID = 从机ID号
        '         uiAddr = 寄存器地址 
        '返 回 值:寄存器的内容（UInt32）（连接为打开或错误时返回0）
        '功能描述:从server中读取2个输出寄存器的内容（UInt32）
        '=====================================================================
        Public Function OUT_ReadUint32(ByVal ID As Byte, ByVal uiAddr As UInt16) As UInt32
            Return CUInt(OUT_ReadUint16(ID, uiAddr + 1)) * &H10000 + CUInt(OUT_ReadUint16(ID, uiAddr))
        End Function




        '=====================================================================
        '函 数 名:OUT_ReadInt32
        '参    数:ID = 从机ID号
        '         uiAddr = 寄存器地址 
        '返 回 值:寄存器的内容（Int32）（连接为打开或错误时返回0）
        '功能描述:从server中读取2个输出寄存器的内容（Int32）
        '=====================================================================
        Public Function OUT_ReadInt32(ByVal ID As Byte, ByVal uiAddr As UInt16) As Int32
            Return ModbusBase.Uint32ToInt32(OUT_ReadUint32(ID, uiAddr))
        End Function




        '=====================================================================
        '函 数 名:OUT_ReadSingle
        '参    数:ID = 从机ID号
        '         uiAddr = 寄存器地址 
        '返 回 值:寄存器的内容（Single）（连接为打开或错误时返回0）
        '功能描述:从server中读取2个输出寄存器的内容（Single）
        '=====================================================================
        Public Function OUT_ReadSingle(ByVal ID As Byte, ByVal uiAddr As UInt16) As Single
            Return ModbusBase.Uint32ToSingle(OUT_ReadUint32(ID, uiAddr))
        End Function




        '=====================================================================
        '函 数 名:OUT_ReadString
        '参    数:ID = 从机ID号
        '         uiAddr = 寄存器地址 
        '         uiNum = 字节数量（2-N）
        '返 回 值:寄存器的内容（String）（连接为打开或错误时返回0）
        '功能描述:从server中读取输出寄存器的内容（String）
        '=====================================================================
        Public Function OUT_ReadString(ByVal ID As Byte, ByVal uiAddr As UInt16, ByVal uiNum As UInt16)
            Dim str1 As String
            Dim tmp As UInt16
            Dim H, L As Byte
            Dim i As UInt16

            str1 = ""
            For i = uiAddr To uiAddr + uiNum \ 2 - 1
                tmp = OUT_ReadUint16(ID, i)
                L = CByte(tmp Mod &H100)
                H = CByte(tmp \ &H100)
                str1 &= Chr(L)
                str1 &= Chr(H)
            Next

            If (uiNum And &H1) = &H1 Then    '不是整数字（单数字节）
                i = uiAddr + uiNum \ 2
                tmp = tmp = OUT_ReadUint16(ID, i)

                If tmp <> 0 Then
                    L = tmp And &HFF
                    str1 &= Chr(L)
                End If
            End If

            Return str1
        End Function




        '=====================================================================
        '函 数 名:OUT_ReadBufferUint16
        '参    数:ID = 从机ID号
        '         uiAddr = 寄存器地址
        '返 回 值:寄存器的内容（UInt16）（连接为打开或错误时返回0）
        '功能描述:从server中读取一个输出寄存器的内容（UInt16）
        '=====================================================================
        Public Function OUT_ReadBufferUint16(ByVal ID As Byte, ByVal uiAddr As UInt16) As UInt16
            SyncLock (threadLock)   '线程互斥
                If OUT_IsBlockAvailable() = True Then '输出寄存器块有效
                    Dim i As UInt16

                    For i = 0 To Reg.RegBlockOUT.Count - 1  '遍历块
                        If Reg.RegBlockOUT(i).ID = ID Then
                            If uiAddr >= Reg.RegBlockOUT(i).StartAddr And uiAddr <= Reg.RegBlockOUT(i).EndAddr Then
                                Return Reg.RegBlockOUT(i).ItemWrite(uiAddr - Reg.RegBlockOUT(i).StartAddr)
                            End If
                        End If
                    Next
                End If
            End SyncLock

            Return 0
        End Function




        '=====================================================================
        '函 数 名:OUT_ReadBufferInt16
        '参    数:ID = 从机ID号
        '         uiAddr = 寄存器地址 
        '返 回 值:寄存器的内容（Int16）（连接为打开或错误时返回0）
        '功能描述:从server中读取1个输出寄存器的内容（Int16）
        '=====================================================================
        Public Function OUT_ReadBufferInt16(ByVal ID As Byte, ByVal uiAddr As UInt16) As Int16
            Return ModbusBase.Uint16ToInt16(OUT_ReadBufferUint16(ID, uiAddr))
        End Function




        '=====================================================================
        '函 数 名:OUT_ReadBufferUint32
        '参    数:ID = 从机ID号
        '         uiAddr = 寄存器地址 
        '返 回 值:寄存器的内容（UInt32）（连接为打开或错误时返回0）
        '功能描述:从server中读取2个输出寄存器的内容（UInt32）
        '=====================================================================
        Public Function OUT_ReadBufferUint32(ByVal ID As Byte, ByVal uiAddr As UInt16) As UInt32
            Return CUInt(OUT_ReadBufferUint16(ID, uiAddr + 1)) * &H10000 + CUInt(OUT_ReadBufferUint16(ID, uiAddr))
        End Function




        '=====================================================================
        '函 数 名:OUT_ReadBufferInt32
        '参    数:ID = 从机ID号
        '         uiAddr = 寄存器地址 
        '返 回 值:寄存器的内容（Int32）（连接为打开或错误时返回0）
        '功能描述:从server中读取2个输出寄存器的内容（Int32）
        '=====================================================================
        Public Function OUT_ReadBufferInt32(ByVal ID As Byte, ByVal uiAddr As UInt16) As Int32
            Return ModbusBase.Uint32ToInt32(OUT_ReadBufferUint32(ID, uiAddr))
        End Function




        '=====================================================================
        '函 数 名:OUT_ReadBufferSingle
        '参    数:ID = 从机ID号
        '         uiAddr = 寄存器地址 
        '返 回 值:寄存器的内容（Single）（连接为打开或错误时返回0）
        '功能描述:从server中读取2个输出寄存器的内容（Single）
        '=====================================================================
        Public Function OUT_ReadBufferSingle(ByVal ID As Byte, ByVal uiAddr As UInt16) As Single
            Return ModbusBase.Uint32ToSingle(OUT_ReadBufferUint32(ID, uiAddr))
        End Function




        '=====================================================================
        '函 数 名:OUT_ReadBufferString
        '参    数:ID = 从机ID号
        '         uiAddr = 寄存器地址 
        '         uiNum = 字节数量（2-N）
        '返 回 值:寄存器的内容（String）（连接为打开或错误时返回0）
        '功能描述:从server中读取输出寄存器的内容（String）
        '=====================================================================
        Public Function OUT_ReadBufferString(ByVal ID As Byte, ByVal uiAddr As UInt16, ByVal uiNum As UInt16)
            Dim str1 As String
            Dim tmp As UInt16
            Dim H, L As Byte
            Dim i As UInt16

            str1 = ""
            For i = uiAddr To uiAddr + uiNum \ 2 - 1
                tmp = OUT_ReadBufferUint16(ID, i)
                L = CByte(tmp Mod &H100)
                H = CByte(tmp / &H100)
                str1 &= Chr(L)
                str1 &= Chr(H)
            Next

            If (uiNum And &H1) = &H1 Then    '不是整数字（单数字节）
                i = uiAddr + uiNum \ 2
                tmp = tmp = OUT_ReadBufferUint16(ID, i)

                If tmp <> 0 Then
                    L = tmp And &HFF
                    str1 &= Chr(L)
                End If
            End If

            Return str1
        End Function



        '=====================================================================
        '函 数 名:OUT_WriteUint16
        '参    数:ID = 从机ID号
        '         uiAddr = 寄存器地址 
        '         uiData = 寄存器的内容（UInt16）
        '返 回 值:MODBUS_OK/MODBUS_ERR
        '功能描述:向server中读取一个输出寄存器的内容（UInt16）【与当前读回的值不同就触发写命令】
        '=====================================================================
        Public Function OUT_WriteUint16(ByVal ID As Byte, ByVal uiAddr As UInt16, ByVal uiData As UInt16) As UInt16
            SyncLock (threadLock)   '线程互斥
                If OUT_IsBlockAvailable() = True Then '输出寄存器块有效
                    Dim i As UInt16

                    For i = 0 To Reg.RegBlockOUT.Count - 1  '遍历块
                        If Reg.RegBlockOUT(i).ID = ID Then
                            If uiAddr >= Reg.RegBlockOUT(i).StartAddr And uiAddr <= Reg.RegBlockOUT(i).EndAddr Then
                                Reg.RegBlockOUT(i).ItemWrite(uiAddr - Reg.RegBlockOUT(i).StartAddr) = uiData

                                '置写标志
                                If Reg.RegBlockOUT(i).WriteCheckEnable Then
                                    '检查是否需要写
                                    If Reg.RegBlockOUT(i).Item(uiAddr - Reg.RegBlockOUT(i).StartAddr) <> uiData Then
                                        Reg.RegBlockOUT(i).ItemNeedWrite(uiAddr - Reg.RegBlockOUT(i).StartAddr) = True
                                    End If
                                Else
                                    Reg.RegBlockOUT(i).ItemNeedWrite(uiAddr - Reg.RegBlockOUT(i).StartAddr) = True
                                End If

                                Return MODBUS_OK
                            End If
                        End If
                    Next
                Else
                    Return MODBUS_ERR7  '服务未启动
                End If
            End SyncLock

            Return MODBUS_ERR  '错误
        End Function



        '=====================================================================
        '函 数 名:OUT_WriteInt16
        '参    数:ID = 从机ID号
        '         uiAddr = 寄存器地址 
        '         iData = 寄存器的内容（Int16）
        '返 回 值:MODBUS_OK/MODBUS_ERR
        '功能描述:向server中读取一个输出寄存器的内容（Int32）
        '=====================================================================
        Public Function OUT_WriteInt16(ByVal ID As Byte, ByVal uiAddr As UInt16, ByVal iData As Int16) As UInt16
            Return OUT_WriteUint16(ID, uiAddr, ModbusBase.Int16ToUint16(iData))
        End Function



        '=====================================================================
        '函 数 名:OUT_WriteUint32
        '参    数:ID = 从机ID号
        '         uiAddr = 寄存器地址 
        '         ulData = 寄存器的内容（双字）
        '返 回 值:MODBUS_OK/MODBUS_ERR
        '功能描述:向server中读取一个输出寄存器的内容（双字）
        '=====================================================================
        Public Function OUT_WriteUint32(ByVal ID As Byte, ByVal uiAddr As UInt16, ByVal ulData As UInt32) As UInt16
            Dim L As UInt16
            Dim H As UInt16

            H = ulData And &HFFFF
            L = ulData \ &H10000

            Dim err As UInt16

            err = OUT_WriteUint16(ID, uiAddr, H)
            If err <> MODBUS_OK Then
                Return err
            End If

            err = OUT_WriteUint16(ID, uiAddr + 1, L)
            If err <> MODBUS_OK Then
                Return err
            End If

            Return MODBUS_OK
        End Function



        '=====================================================================
        '函 数 名:OUT_WriteInt32
        '参    数:ID = 从机ID号
        '         uiAddr = 寄存器地址 
        '         lData = 寄存器的内容（Int32）
        '返 回 值:MODBUS_OK/MODBUS_ERR
        '功能描述:向server中读取一个输出寄存器的内容（Int32）
        '=====================================================================
        Public Function OUT_WriteInt32(ByVal ID As Byte, ByVal uiAddr As UInt16, ByVal lData As Int32) As UInt16
            Return OUT_WriteUint32(ID, uiAddr, ModbusBase.Int32ToUint32(lData))
        End Function



        '=====================================================================
        '函 数 名:OUT_WriteSingle
        '参    数:ID = 从机ID号
        '         uiAddr = 寄存器地址 
        '         fData = 寄存器的内容（浮点）
        '返 回 值:MODBUS_OK/MODBUS_ERR
        '功能描述:向server中读取一个输出寄存器的内容（浮点）
        '=====================================================================
        Public Function OUT_WriteSingle(ByVal ID As Byte, ByVal uiAddr As UInt16, ByVal fData As Single) As UInt16
            Return OUT_WriteUint32(ID, uiAddr, ModbusBase.SingleToUint32(fData))
        End Function




        '=====================================================================
        '函 数 名:OUT_WriteSingle
        '参    数:ID = 从机ID号
        '         uiAddr = 寄存器地址
        '         strData = 字符串 
        '返 回 值:MODBUS_OK/MODBUS_ERR
        '功能描述:向server中读取一个输出寄存器的内容（String）
        '=====================================================================
        Public Function OUT_WriteString(ByVal ID As Byte, ByVal uiAddr As UInt16, ByVal strData As String) As UInt16
            Dim i As UInt16
            Dim str1, str As String

            str = ""
            str1 = strData

            Dim tmp As UInt16
            tmp = 0

            For i = uiAddr To uiAddr + Len(strData) \ 2 - 1
                str = Mid(str1, 1, 2)
                str1 = IIf(Len(str1) > 2, Mid(str1, 3), "")

                If Len(str) = 2 Then
                    tmp = Asc(Mid(str, 2, 1)) * &H100 + Asc(Mid(str, 1, 1))
                    OUT_WriteUint16(ID, i, tmp)
                End If
            Next

            If Len(str1) = 1 Then     '不是整数字（单数字节）
                i = uiAddr + Len(strData) \ 2 + 1
                tmp = Asc(str1) & Asc(" ") * &H100
                OUT_WriteUint16(ID, i, tmp)
            End If

            OUT_WriteString = 0
        End Function




        '=====================================================================
        '函 数 名:OUT_Enable
        '参    数:ID = 从机ID号
        '         uiAddr = 寄存器地址 
        '返 回 值:
        '功能描述:使能输出寄存器的片区有效性
        '=====================================================================
        Public Sub OUT_Enable(ByVal ID As Byte, ByVal uiAddr As UInt16)
            SyncLock (threadLock)   '线程互斥
                If OUT_IsBlockAvailable() = True Then '输出寄存器块有效
                    Dim i As UInt16

                    For i = 0 To Reg.RegBlockOUT.Count - 1  '遍历块
                        If Reg.RegBlockOUT(i).ID = ID Then
                            If uiAddr >= Reg.RegBlockOUT(i).StartAddr And uiAddr <= Reg.RegBlockOUT(i).EndAddr Then
                                Reg.RegBlockOUT(i).Enable = True
                            End If
                        End If
                    Next
                End If
            End SyncLock
        End Sub




        '=====================================================================
        '函 数 名:OUT_Disable
        '参    数:ID = 从机ID号
        '         uiAddr = 寄存器地址 
        '返 回 值:
        '功能描述:禁用输出寄存器的片区有效性
        '=====================================================================
        Public Sub OUT_Disable(ByVal ID As Byte, ByVal uiAddr As UInt16)
            SyncLock (threadLock)   '线程互斥
                If OUT_IsBlockAvailable() = True Then '输出寄存器块有效
                    Dim i As UInt16

                    For i = 0 To Reg.RegBlockOUT.Count - 1  '遍历块
                        If Reg.RegBlockOUT(i).ID = ID Then
                            If uiAddr >= Reg.RegBlockOUT(i).StartAddr And uiAddr <= Reg.RegBlockOUT(i).EndAddr Then
                                Reg.RegBlockOUT(i).Enable = False
                            End If
                        End If
                    Next
                End If
            End SyncLock
        End Sub




        '=====================================================================
        '函 数 名:OUT_Enable
        '参    数:ID = 从机ID号
        '         uiAddr = 寄存器地址 
        '返 回 值:当前是否使能
        '功能描述:检查输出寄存器的片区使能状态
        '=====================================================================
        Public Function OUT_IsEnable(ByVal ID As Byte, ByVal uiAddr As UInt16) As Boolean
            SyncLock (threadLock)   '线程互斥
                If OUT_IsBlockAvailable() = True Then '输出寄存器块有效
                    Dim i As UInt16

                    For i = 0 To Reg.RegBlockOUT.Count - 1  '遍历块
                        If Reg.RegBlockOUT(i).ID = ID Then
                            If uiAddr >= Reg.RegBlockOUT(i).StartAddr And uiAddr <= Reg.RegBlockOUT(i).EndAddr Then
                                Return Reg.RegBlockOUT(i).Enable
                            End If
                        End If
                    Next
                End If
            End SyncLock

            Return False
        End Function



        '=====================================================================
        '函 数 名:OUT_IsAllWrited
        '参    数:
        '返 回 值:True/False
        '功能描述:是否所有的输出寄存器值都已经输出完成
        '=====================================================================
        Public Function OUT_IsAllWrited() As Boolean
            SyncLock (threadLock)   '线程互斥
                If OUT_IsBlockAvailable() = True Then '输出寄存器块有效
                    Dim i As UInt16

                    For i = 0 To Reg.RegBlockOUT.Count - 1  '遍历块
                        If Reg.RegBlockOUT(i).Enable = True Then '使能
                            If Not IsNothing(Reg.RegBlockOUT(i).ItemNeedWrite) Then
                                If Reg.RegBlockOUT(i).ItemNeedWrite.Count > 0 Then
                                    Dim k As UInt16

                                    For k = 0 To Reg.RegBlockOUT(i).ItemNeedWrite.Count - 1  '遍历寄存器
                                        If Reg.RegBlockOUT(i).ItemNeedWrite(k) = True Then
                                            Return False
                                        End If
                                    Next
                                End If
                            End If
                        End If
                    Next
                End If
            End SyncLock

            Return True
        End Function



    End Class
End Namespace